<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  

  
  <title>在路上</title>
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
  <meta property="og:type" content="website">
<meta property="og:title" content="在路上">
<meta property="og:url" content="http://yoursite.com/page/2/index.html">
<meta property="og:site_name" content="在路上">
<meta property="og:locale" content="zh-CN">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="在路上">
  
    <link rel="alternate" href="/atom.xml" title="在路上" type="application/atom+xml">
  
  
    <link rel="icon" href="/favicon.png">
  
  
    <link href="//fonts.googleapis.com/css?family=Source+Code+Pro" rel="stylesheet" type="text/css">
  
  <link rel="stylesheet" href="/css/style.css">
</head>

<body>
  <div id="container">
    <div id="wrap">
      <header id="header">
  <div id="banner"></div>
  <div id="header-outer" class="outer">
    <div id="header-title" class="inner">
      <h1 id="logo-wrap">
        <a href="/" id="logo">在路上</a>
      </h1>
      
        <h2 id="subtitle-wrap">
          <a href="/" id="subtitle">一位崇尚价值投资的 Web 开发者</a>
        </h2>
      
    </div>
    <div id="header-inner" class="inner">
      <nav id="main-nav">
        <a id="main-nav-toggle" class="nav-icon"></a>
        
          <a class="main-nav-link" href="/">Home</a>
        
          <a class="main-nav-link" href="/archives">Archives</a>
        
      </nav>
      <nav id="sub-nav">
        
          <a id="nav-rss-link" class="nav-icon" href="/atom.xml" title="RSS Feed"></a>
        
        <a id="nav-search-btn" class="nav-icon" title="搜索"></a>
      </nav>
      <div id="search-form-wrap">
        <form action="//google.com/search" method="get" accept-charset="UTF-8" class="search-form"><input type="search" name="q" class="search-form-input" placeholder="Search"><button type="submit" class="search-form-submit">&#xF002;</button><input type="hidden" name="sitesearch" value="http://yoursite.com"></form>
      </div>
    </div>
  </div>
</header>
      <div class="outer">
        <section id="main">
  
    <article id="post-decodeURIComponent" class="article article-type-post" itemscope itemprop="blogPost">
  <div class="article-meta">
    <a href="/2015/06/30/decodeURIComponent/" class="article-date">
  <time datetime="2015-06-29T16:00:00.000Z" itemprop="datePublished">2015-06-30</time>
</a>
    
  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/2015/06/30/decodeURIComponent/">捉虫记之decodeURIComponent</a>
    </h1>
  

      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <p>DataEye游戏分析平台有个实时日志功能，用于展示接收来自游戏客户端SDK上报数据以及游戏开发商使用HTTP接口发送的数据。</p>
<h2 id="第一版"><a href="#第一版" class="headerlink" title="第一版"></a>第一版</h2><p>数据库收到什么样的JSON数据就直接返回给前端展示，终于有一天客户反馈页面脚本报错。</p>
<p>一番调查，原来是数据库中存在特殊字符，于是要求后台全部url encode一次，前端decode就可以了。验证OK，于是测试发布。直到有一天用户反馈部分数据没有decode，还是类似%25XX的字符。</p>
<h2 id="第二版"><a href="#第二版" class="headerlink" title="第二版"></a>第二版</h2><p>一番调查，原来是用户使用的是HTTP接口发送数据而不是SDK，所以部分数据自己encode了一次。然后服务器返回给前端页面的就是encode两次的数据。</p>
<p>不就encode两次的问题嘛，页面decode两次就可以了嘛！于是无脑地吧decodeURIComponent(str)全部替换成了decodeURIComponent(decodeURIComponent(str))，发现问题居然解决了，找了另外的客户数据测试了下，验证也OK，于是发布。直到有一天某个另外的客户反馈页面脚本报错。</p>
<h2 id="第三版"><a href="#第三版" class="headerlink" title="第三版"></a>第三版</h2><p>一番调查，根据URI Malformed Error这个罪证google得知，原来是源字符串不是合法的URL编码的字符串。虽然提示已经相当的明确了，但由于JSON数据量不少，字段较多，而且有些地方只需要decode一次，有些地方又需要decode两次，定位问题还是花了一些时间。最后发现罪魁祸首居然是用户输入的内容<code>14%</code>。</p>
<h2 id="解决方法"><a href="#解决方法" class="headerlink" title="解决方法"></a>解决方法</h2><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">safeDecodeURIComponent</span>(<span class="params">str</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (!str || <span class="keyword">typeof</span> str !== <span class="string">'string'</span>) <span class="keyword">return</span> <span class="built_in">String</span>(str)</span><br><span class="line"></span><br><span class="line">    <span class="comment">// encodeURIComponent不编码字符有71个：!， '，(，)，*，-，.，_，~，0-9，a-z，A-Z</span></span><br><span class="line">    <span class="keyword">var</span> twiceEncodedReg = <span class="regexp">/^(%\w&#123;4,4&#125;|[!'()*\-._~0-9a-z])+$/gi</span></span><br><span class="line">    <span class="keyword">var</span> encodedReg = <span class="regexp">/^(%\w&#123;2,2&#125;|[!'()*\-._~0-9a-z])+$/gi</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 这里用try catch是因为即使正则匹配也有可能解码异常，异常或者不匹配时返回源字符串即可</span></span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (twiceEncodedReg.test(str)) <span class="keyword">return</span> <span class="built_in">decodeURIComponent</span>(<span class="built_in">decodeURIComponent</span>(str))</span><br><span class="line">        <span class="keyword">if</span> (encodedReg.test(str)) <span class="keyword">return</span> <span class="built_in">decodeURIComponent</span>(str)</span><br><span class="line">    &#125; <span class="keyword">catch</span>(e) &#123;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> str</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h2 id="相关链接"><a href="#相关链接" class="headerlink" title="相关链接"></a>相关链接</h2><blockquote>
<ul>
<li><a href="http://xkr.us/articles/javascript/encode-compare/" target="_blank" rel="noopener">Comparing escape(), encodeURI(), and encodeURIComponent()</a></li>
<li><a href="http://www.cnblogs.com/jhxk/articles/1634359.html" target="_blank" rel="noopener">http://www.cnblogs.com/jhxk/articles/1634359.html</a></li>
</ul>
</blockquote>
<h2 id="最后的总结"><a href="#最后的总结" class="headerlink" title="最后的总结"></a>最后的总结</h2><p>基础知识不扎实导致无法深入剖析问题所在，解决方案自以为然也就不足为怪了。</p>
<p>—-7-21更新—-</p>
<h2 id="后续"><a href="#后续" class="headerlink" title="后续"></a>后续</h2><p>果然还是有后续！昨天突然一个客户又反馈有乱码，一番研究发现用户输入部分编码，部分未编码。上面的方法就不凑效了，于是花了几分钟做了如下改进：</p>
<figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">safeDecodeURIComponent</span>(<span class="params">str</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">var</span> lastResult = str, current</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">while</span>((current = <span class="built_in">decodeURIComponent</span>(lastResult)) != lastResult) &#123;</span><br><span class="line">            lastResult = current</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">catch</span>(e) &#123;&#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> lastResult</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

      
    </div>
    <footer class="article-footer">
      <a data-url="http://yoursite.com/2015/06/30/decodeURIComponent/" data-id="cjf865ufm000t8wp1itd3yxux" class="article-share-link">Share</a>
      
      
  <ul class="article-tag-list"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/JavaScript/">JavaScript</a></li></ul>

    </footer>
  </div>
  
</article>


  
    <article id="post-白鹭引擎无废话快速上手" class="article article-type-post" itemscope itemprop="blogPost">
  <div class="article-meta">
    <a href="/2015/05/03/白鹭引擎无废话快速上手/" class="article-date">
  <time datetime="2015-05-02T16:00:00.000Z" itemprop="datePublished">2015-05-03</time>
</a>
    
  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/2015/05/03/白鹭引擎无废话快速上手/">白鹭引擎无废话快速上手</a>
    </h1>
  

      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <p>如果你从未听说白鹭引擎，也不了解其用途，那么本文可能并不适合你。阅读之前请确保你已了解白鹭引擎是一套基于<code>TypeScript</code>的游戏开发引擎，本文讲述了使用白鹭引擎开发HTML5项目的一般流程。</p>
<h2 id="安装白鹭引擎"><a href="#安装白鹭引擎" class="headerlink" title="安装白鹭引擎"></a>安装白鹭引擎</h2><p>安装很简单，不论是windows还是mac，前往官网下载白鹭引擎的安装包，解压安装即可。参考链接：</p>
<blockquote>
<ul>
<li><a href="http://docs.egret-labs.org/post/quitestart/install/installwin.html" target="_blank" rel="noopener">Windows</a></li>
<li><a href="http://docs.egret-labs.org/post/quitestart/install/instalformac.html" target="_blank" rel="noopener">Mac</a></li>
</ul>
</blockquote>
<p>友情提示：至本教程完成时，白鹭引擎尚未提供适用于Linux系统的安装包。</p>
<h2 id="验证安装过程"><a href="#验证安装过程" class="headerlink" title="验证安装过程"></a>验证安装过程</h2><p>安装完成之后，打开终端输入<code>egret</code>，命令行会展示用法提示以及command列表。</p>
<p>如果egret命令未找到，说明你的安装过程出错了。</p>
<p>白鹭引擎的命令行工具是基于<code>Node.js</code>开发的，其源代码也是开源的。如果对某个命令有疑问或者对其实现感兴趣，可以直接查看其源代码更为方便。具体位置在白鹭引擎安装路径下的<code>/egret/tools/lib/tools</code>，比如<code>build.js</code>和<code>publish.js</code>分别对应<code>egret build</code>和<code>egret publish</code>命令。或者前往GitHub查看<a href="https://github.com/egret-labs/egret-core/tree/master/tools/lib/tools" target="_blank" rel="noopener">https://github.com/egret-labs/egret-core/tree/master/tools/lib/tools</a></p>
<h2 id="创建项目"><a href="#创建项目" class="headerlink" title="创建项目"></a>创建项目</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">egret create HelloWorld</span><br></pre></td></tr></table></figure>
<h2 id="编译项目"><a href="#编译项目" class="headerlink" title="编译项目"></a>编译项目</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">egret build HelloWorld</span><br></pre></td></tr></table></figure>
<p>将TypeScript的源代码编译为原生JavaScript代码，默认编译为HTML5项目。<strong>如果需要编译为native项目</strong>，则加上<code>--runtime native</code>参数。</p>
<h2 id="运行项目"><a href="#运行项目" class="headerlink" title="运行项目"></a>运行项目</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">egret startserver HelloWorld</span><br></pre></td></tr></table></figure>
<p>白鹭引擎内部创建了一个基于Express的WebServer，当运行此命令后会自动在默认刘传奇中打开项目首页。</p>
<h2 id="发布项目"><a href="#发布项目" class="headerlink" title="发布项目"></a>发布项目</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">egret publish HelloWorld</span><br></pre></td></tr></table></figure>
<h2 id="其它"><a href="#其它" class="headerlink" title="其它"></a>其它</h2><p>HelloWorld之后继续深入，请阅读<a href="http://docs.egret-labs.org/post/quitestart/helloworld/helloworld2.html" target="_blank" rel="noopener">http://docs.egret-labs.org/post/quitestart/helloworld/helloworld2.html</a>。</p>
<p>白鹭引擎相关开源项目：<a href="https://github.com/egret-labs" target="_blank" rel="noopener">https://github.com/egret-labs</a></p>
<h2 id="关于TypeScript"><a href="#关于TypeScript" class="headerlink" title="关于TypeScript"></a>关于TypeScript</h2><p>如果读者需要使用白鹭引擎开发游戏，那么系统地学习TypeScript是必经之路。虽然TypeScript的入门门槛不算高，但是完全掌握也需不少时间，不过对于开发一款游戏来说，掌握这门语言带来的收益应该远远大于学习的成本。</p>

      
    </div>
    <footer class="article-footer">
      <a data-url="http://yoursite.com/2015/05/03/白鹭引擎无废话快速上手/" data-id="cjf865ug3001o8wp100vatspu" class="article-share-link">Share</a>
      
      
  <ul class="article-tag-list"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/JavaScript/">JavaScript</a></li></ul>

    </footer>
  </div>
  
</article>


  
    <article id="post-requirejs定义模块的两种方式" class="article article-type-post" itemscope itemprop="blogPost">
  <div class="article-meta">
    <a href="/2015/04/09/requirejs定义模块的两种方式/" class="article-date">
  <time datetime="2015-04-08T16:00:00.000Z" itemprop="datePublished">2015-04-09</time>
</a>
    
  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/2015/04/09/requirejs定义模块的两种方式/">RequireJS定义模块常用的两种方式</a>
    </h1>
  

      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <p>RequieJS是一个遵循AMD规范的模块加载器，其定义模块的方式非常简洁，如下：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">define(id?, dependencies?, factory);</span><br></pre></td></tr></table></figure>
<blockquote>
<ul>
<li>id为模块名称，字符串，可选参数</li>
<li>dependencies为本模块依赖的其它模块，数组，可选</li>
<li>factory为模块的实现，可以为一个对象或者函数，必填</li>
</ul>
</blockquote>
<p>最常见的定义模块的方式如下：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">define([&apos;jquery&apos;], function($)&#123;</span><br><span class="line">    var myModule = &#123;</span><br><span class="line">        add: function(a, b) &#123;</span><br><span class="line">               return a + b</span><br><span class="line">       &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    /*</span><br><span class="line">     * 这里将对象直接返回即可</span><br><span class="line">     * 返回函数也是可以的</span><br><span class="line">     * 如果没有任何返回值，那么模块作为依赖被引入的时候就是undefined</span><br><span class="line">     */</span><br><span class="line">    return myModule</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>
<p>另外还有一种类似CommonJS的方式</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">define([&apos;exports&apos;], function(exports)&#123;</span><br><span class="line">    exports.add =  function(a, b) &#123;</span><br><span class="line">        return a + b</span><br><span class="line">    &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>
<p>这种方式将CommonJS模块的转换为AMD风格的模块十分方便。</p>
<p>如果一个模块同时兼容AMD、CommonJS或者无模块系统的传统页面，那么就需要用到<a href="https://github.com/umdjs/umd/blob/master/returnExports.js" target="_blank" rel="noopener">UMD</a></p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">// root在浏览器里代表window对象，node里面则是global</span><br><span class="line">;(function (root, factory) &#123;</span><br><span class="line">    // AMD</span><br><span class="line">    if (typeof define === &apos;function&apos; &amp;&amp; define.amd) &#123;</span><br><span class="line">        define([], factory)</span><br><span class="line">    &#125; else if (typeof exports === &apos;object&apos;) &#123;</span><br><span class="line">        // CommonJS</span><br><span class="line">        module.exports = factory()</span><br><span class="line">    &#125; else &#123;</span><br><span class="line">        // 暴露出的全局变量</span><br><span class="line">        root.moduleName = factory()</span><br><span class="line">  &#125;</span><br><span class="line">&#125;(this, function () &#123;</span><br><span class="line">    var myModule = &#123;&#125;</span><br><span class="line">    return myModule</span><br><span class="line">&#125;))</span><br></pre></td></tr></table></figure>
<p>参考文档</p>
<hr>
<blockquote>
<ul>
<li><a href="http://segmentfault.com/a/1190000000761330" target="_blank" rel="noopener">AMD模块定义规范</a></li>
<li><a href="http://www.cnblogs.com/snandy/archive/2012/03/12/2390782.html" target="_blank" rel="noopener">AMD：浏览器中的模块规范</a></li>
</ul>
</blockquote>

      
    </div>
    <footer class="article-footer">
      <a data-url="http://yoursite.com/2015/04/09/requirejs定义模块的两种方式/" data-id="cjf865ufs00148wp1vekuw58t" class="article-share-link">Share</a>
      
      
  <ul class="article-tag-list"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/JavaScript/">JavaScript</a></li></ul>

    </footer>
  </div>
  
</article>


  
    <article id="post-isArguments" class="article article-type-post" itemscope itemprop="blogPost">
  <div class="article-meta">
    <a href="/2015/04/02/isArguments/" class="article-date">
  <time datetime="2015-04-01T16:00:00.000Z" itemprop="datePublished">2015-04-02</time>
</a>
    
  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/2015/04/02/isArguments/">美妙的函数之isArguments</a>
    </h1>
  

      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <p>判断变量是否为Arguments对象没有想象中的那么简单，你可能会使用下面这种方法：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Object.prototype.toString.call(value) === &apos;[Object Arguments]&apos;</span><br></pre></td></tr></table></figure>
<p>我们先来看看著名的es5-shim是如何实现的。</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">var _toString = ObjectPrototype.toString;</span><br><span class="line"></span><br><span class="line">var isFunction = function(val) &#123;</span><br><span class="line">    return ObjectPrototype.toString.call(val) === &apos;[object Function]&apos;;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">var isArguments = function isArguments(value) &#123;</span><br><span class="line">    var str = _toString.call(value);</span><br><span class="line">    var isArgs = str === &apos;[object Arguments]&apos;;</span><br><span class="line">    if (!isArgs) &#123;</span><br><span class="line">        isArgs = !isArray(value) &amp;&amp; value !== null &amp;&amp; typeof value === &apos;object&apos; &amp;&amp; typeof value.length === &apos;number&apos; &amp;&amp; value.length &gt;= 0 &amp;&amp; isFunction(value.callee);</span><br><span class="line">    &#125;</span><br><span class="line">    return isArgs;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>因为某些浏览器的<a href="https://github.com/lodash/lodash/blob/master/lodash.src.js#L1004" target="_blank" rel="noopener">buggy</a>行为，这个方法不得不这么长。再看看<a href="https://github.com/lodash/lodash/blob/master/lodash.src.js#L8480" target="_blank" rel="noopener">lodash</a>的实现：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">var MAX_SAFE_INTEGER = Math.pow(2, 53) - 1;</span><br><span class="line"></span><br><span class="line">function isObjectLike(value) &#123;</span><br><span class="line">    return !!value &amp;&amp; typeof value == &apos;object&apos;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">function isLength(value) &#123;</span><br><span class="line">  return typeof value == &apos;number&apos; &amp;&amp; value &gt; -1 &amp;&amp; value % 1 == 0 &amp;&amp; value &lt;= MAX_SAFE_INTEGER;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">function isArguments(value) &#123;</span><br><span class="line">  var length = isObjectLike(value) ? value.length : undefined;</span><br><span class="line">  return isLength(length) &amp;&amp; objToString.call(value) == argsTag;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">// Fallback for environments without a `toStringTag` for `arguments` objects.</span><br><span class="line">if (!support.argsTag) &#123;</span><br><span class="line">  isArguments = function(value) &#123;</span><br><span class="line">    var length = isObjectLike(value) ? value.length : undefined;</span><br><span class="line">    return isLength(length) &amp;&amp; hasOwnProperty.call(value, &apos;callee&apos;) &amp;&amp;</span><br><span class="line">      !propertyIsEnumerable.call(value, &apos;callee&apos;);</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>对比两种实现，es5-shim仅仅只是做了arguments对象特有的属性检测，而lodash的实现不论是从代码可读性还是严谨方面来考察都是极其完美的。</p>

      
    </div>
    <footer class="article-footer">
      <a data-url="http://yoursite.com/2015/04/02/isArguments/" data-id="cjf865ufr00128wp1nmlcuol3" class="article-share-link">Share</a>
      
      
  <ul class="article-tag-list"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/JavaScript/">JavaScript</a></li></ul>

    </footer>
  </div>
  
</article>


  
    <article id="post-程序员的强迫症" class="article article-type-post" itemscope itemprop="blogPost">
  <div class="article-meta">
    <a href="/2014/07/29/程序员的强迫症/" class="article-date">
  <time datetime="2014-07-28T16:00:00.000Z" itemprop="datePublished">2014-07-29</time>
</a>
    
  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/2014/07/29/程序员的强迫症/">程序员的强迫症</a>
    </h1>
  

      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <p>编程追求效率，却也讲究习惯。习惯改变之难，还真不能说<code>那就不是个事儿</code>，在技术圈子尤甚。</p>
<p>当然，JavaScript这个圈子也不例外，比如这些：</p>
<ul>
<li><a href="http://www.zhihu.com/question/20298345" target="_blank" rel="noopener">JavaScript语句后面应该加分号吗？</a></li>
<li><a href="http://www.nczonline.net/blog/2013/06/25/eval-isnt-evil-just-misunderstood/" target="_blank" rel="noopener">eval is evil?</a></li>
<li><a href="http://javascriptweblog.wordpress.com/2011/02/07/truth-equality-and-javascript/" target="_blank" rel="noopener">== or ===?</a></li>
<li>…</li>
</ul>
<p>上面的话题，个个是G点，分分钟引发高潮。就犹如<code>Vim和Emacs</code>、<code>Windows和Linux</code>等话题，一旦开篇随之而来的就是引经据典、长篇大论、你死我活、百态尽出。</p>
<p>作为有格调的程序员，我们应追求效率。而非沉浸于无谓的争论，无端的争论往往因为恪守习惯，不善变通。</p>
<p>就上面的话题进行争论，或许还有些”技术主义情怀”。但是下面的这些纯感性的<strong>习惯</strong>呢？不妨先看看：</p>
<ul>
<li>缩进用tab还是用空格，你喜欢哪一种?</li>
<li>tab等于两个还是四个空格，你喜欢哪一种？</li>
<li>对象的键值是紧凑连接，还是在冒号后加一个空格，你喜欢哪一种？</li>
<li>变量声明，单行还是多行，你又喜欢哪一种？</li>
<li>单行代码块的花括号，加还是不加？</li>
<li>…</li>
</ul>
<p>这种<strong>习惯</strong>多如牛毛，以至“知音”着实难寻，因为实在是众口难调、各有所爱。也难怪<strong>Web Storm</strong>等编辑器在Code Style上花尽心思。</p>
<p>一味地拘泥于习惯，在团队协作时就会患上<strong>强迫症</strong>。比如在别人的代码后面加个分号、补个空格，多加个空白行等等诸如此类。如果赶上心情好、情绪佳，或许会再花上一两小时，与他人舌战一番，定要分个胜负。</p>
<p>此种行为既“无伤大雅”，也非“罪恶滔天”，情不自禁而已。不然“看着心里难受”，这种难受之于程序员，比1px的像素差之于产品经理可来的猛烈多了。于是乎加分号、补空格、按换行、删注释、调整顺序，忙得不亦乐乎。待大功告成，仿佛看见了光，解救了天解救了地也解救了自己，心情倍儿爽。</p>
<p>如果你还在做这种“解救众生”的活，不妨试试JSHint来换个活法儿吧。</p>
<h2 id="什么是JSHint"><a href="#什么是JSHint" class="headerlink" title="什么是JSHint"></a>什么是JSHint</h2><p><a href="http://www.jshint.com/about/" target="_blank" rel="noopener">JSHint</a>是一个用于检查JavaScript代码错误和潜在问题的工具。通过简单的配置，它可以强制约束团队保持一致的编码风格。</p>
<h2 id="JSHint配置文件的格式"><a href="#JSHint配置文件的格式" class="headerlink" title="JSHint配置文件的格式"></a>JSHint配置文件的格式</h2><p>在项目根目录新加一个名称为<strong>.jshintrc</strong>的文件，加入相关配置即可，典型的配置如下：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">	<span class="string">"node"</span>: <span class="literal">true</span>,</span><br><span class="line">	<span class="string">"eqeqeq"</span>: <span class="literal">true</span>,</span><br><span class="line">	<span class="string">"forin"</span>: <span class="literal">false</span>,</span><br><span class="line">	<span class="string">"plusplus"</span>: <span class="literal">false</span>,</span><br><span class="line">	<span class="string">"undef"</span>: <span class="literal">true</span>,</span><br><span class="line">	<span class="string">"unused"</span>: <span class="literal">true</span>,</span><br><span class="line">	<span class="string">"strict"</span>: <span class="literal">false</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>json文件中的每一项都对应着一条规则。如果代码违反了规则，编辑器会给出错误提示。这方面Web Storm非常不错。JSHint详细配置文档，请看<a href="http://www.jshint.com/docs/options/" target="_blank" rel="noopener">这里</a></p>
<p>每个文件夹可以有单独的.jshintrc，作用范围就是其下JavaScript代码。还可以使用<strong>.jshintignore</strong>来排除将规则应用到当前目录指定的文件和文件夹。</p>
<h2 id="如何在Web-Storm中使用JSHint"><a href="#如何在Web-Storm中使用JSHint" class="headerlink" title="如何在Web Storm中使用JSHint"></a>如何在Web Storm中使用JSHint</h2><blockquote>
<ul>
<li><code>CMD + ,</code> 打开偏好设置，输入JSHint搜索习惯配置项</li>
<li>选中Inspections&gt;JavaScript&gt;Code quality tools&gt;JSHint</li>
<li>点击JavaScript&gt;Code quality tools&gt;JSHint，勾选enable和use config file(default)即可</li>
</ul>
</blockquote>
<h2 id="配置详解"><a href="#配置详解" class="headerlink" title="配置详解"></a>配置详解</h2><h3 id="强制选项-enforcing-options"><a href="#强制选项-enforcing-options" class="headerlink" title="强制选项(enforcing options)"></a>强制选项(enforcing options)</h3><p>设置为true时，应用规则，对代码约束更严格。</p>
<table>
<thead>
<tr>
<th>配置项</th>
<th>释义</th>
<th>好处</th>
</tr>
</thead>
<tbody>
<tr>
<td> bitwise</td>
<td>禁止按位操作</td>
<td>避免&amp;和&amp;&amp;手误</td>
</tr>
<tr>
<td>camelcase</td>
<td>变量名只允许驼峰camelCase和UPPER_CASE风格</td>
<td></td>
</tr>
<tr>
<td>curly</td>
<td>单行代码块必须使用分号</td>
<td>防止潜在bug</td>
</tr>
<tr>
<td>eqeqeq</td>
<td>禁止使用==,!=</td>
<td>避免类型转换</td>
</tr>
<tr>
<td>es3</td>
<td>兼容IE 6/7/8/9</td>
<td></td>
</tr>
<tr>
<td>forin</td>
<td>内部必须使用hasOwnProperty</td>
<td>防止遍历原型链上的其它属性</td>
</tr>
<tr>
<td>freeze</td>
<td>禁止覆写原生对象</td>
<td></td>
</tr>
<tr>
<td>immend</td>
<td>(function(){}())</td>
<td></td>
</tr>
<tr>
<td>indent</td>
<td>2/4</td>
<td></td>
</tr>
<tr>
<td>latedef</td>
<td>禁止未定义而使用</td>
<td>防止潜在bug</td>
</tr>
<tr>
<td>newcap</td>
<td>类名首字母大写</td>
<td></td>
</tr>
<tr>
<td>noarg</td>
<td>禁止使用arguments.caller和arguments.callee</td>
<td>难以优化，且已经被废除</td>
</tr>
<tr>
<td>noempty</td>
<td>禁止出现空代码块</td>
<td></td>
</tr>
<tr>
<td>nonew</td>
<td>禁止调用构造函数而不赋值</td>
<td></td>
</tr>
<tr>
<td>plusplus</td>
<td>禁止使用++, –</td>
<td>不同语言含义不一致</td>
</tr>
<tr>
<td>quotmark</td>
<td>true/single/double</td>
<td></td>
</tr>
<tr>
<td>undef</td>
<td>禁止使用未定义的遍历</td>
<td></td>
</tr>
<tr>
<td>unused</td>
<td>禁止出现未使用的变量</td>
<td>设置为vars不检查函数参数</td>
</tr>
<tr>
<td>strict</td>
<td>所有的<strong>函数</strong>都会使用es5的严格模式</td>
<td></td>
</tr>
<tr>
<td>maxparams</td>
<td>最多的形参</td>
<td>5</td>
</tr>
<tr>
<td>maxdepth</td>
<td>嵌套深度</td>
<td></td>
</tr>
<tr>
<td>maxstatements</td>
<td>最多声明</td>
<td></td>
</tr>
<tr>
<td>maxcomplexity</td>
<td>cyclomatic complexity</td>
<td></td>
</tr>
<tr>
<td>maxlen</td>
<td>设置一行最大的长度</td>
<td></td>
</tr>
</tbody>
</table>
<h3 id="宽松选项-relaxing-options"><a href="#宽松选项-relaxing-options" class="headerlink" title="宽松选项(relaxing options)"></a>宽松选项(relaxing options)</h3><p>设置为true时，应用规则，对代码约束更宽松。</p>
<table>
<thead>
<tr>
<th>配置项</th>
<th>解释</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>asi</td>
<td>忽略句尾分号警告</td>
<td>扩展阅读：<a href="http://blog.izs.me/post/2353458699/an-open-letter-to-javascript-leaders-regarding" target="_blank" rel="noopener">1</a> , <a href="http://inimino.org/~inimino/blog/javascript_semicolons" target="_blank" rel="noopener">2</a></td>
</tr>
<tr>
<td>boss</td>
<td>会允许在if，for，while里面编写赋值语句</td>
<td>不检查判断条件中得赋值</td>
</tr>
<tr>
<td>debug</td>
<td>忽略debug语句</td>
<td></td>
</tr>
<tr>
<td>eqnull</td>
<td>允许 == null</td>
<td></td>
</tr>
<tr>
<td>esnext</td>
<td>允许ES6语法</td>
<td><a href="http://wiki.ecmascript.org/doku.php?id=harmony:specification_drafts" target="_blank" rel="noopener">ES6草案</a></td>
</tr>
<tr>
<td>evil</td>
<td>忽略evil警告</td>
<td></td>
</tr>
<tr>
<td>expr</td>
<td>在赋值或者函数调用的地方允许出现表达式</td>
<td>大部分情况是手误</td>
</tr>
<tr>
<td>funcscope</td>
<td>允许在控制流程if等中定义变量</td>
<td>默认为false</td>
</tr>
<tr>
<td>globalstrict</td>
<td>忽略全局使用strict的警告</td>
<td></td>
</tr>
<tr>
<td>iterator</td>
<td>忽略使用<strong>iterator</strong>属性警告</td>
<td>并非所有浏览器支持</td>
</tr>
<tr>
<td>lastsemic</td>
<td>仅允许单行代码块不使用分号</td>
<td></td>
</tr>
<tr>
<td>laxbreak</td>
<td>忽略不安全的换行告警</td>
<td></td>
</tr>
<tr>
<td>laxcomma</td>
<td>忽略逗号风格告警</td>
<td></td>
</tr>
<tr>
<td>loopfunc</td>
<td>忽略在循环中定义函数告警</td>
<td>建议设置为false</td>
</tr>
<tr>
<td>maxerr</td>
<td>设置JSHint最多警告数目</td>
<td>默认50</td>
</tr>
<tr>
<td>moz</td>
<td>表明这是Mozilla扩展</td>
<td><a href="https://developer.mozilla.org/en-US/docs/JavaScript/New_in_JavaScript/1.7" target="_blank" rel="noopener">New in JavaScript 1.7</a></td>
</tr>
<tr>
<td>multistr</td>
<td>忽略多行字符串告警</td>
<td></td>
</tr>
<tr>
<td>notypeof</td>
<td>忽略不正确的typeof比较</td>
<td>默认为false，不用管</td>
</tr>
<tr>
<td>proto</td>
<td>忽略使用<strong>proto</strong> 属性</td>
<td></td>
</tr>
<tr>
<td>scripturl</td>
<td>忽略使用”javascript:…”告警</td>
<td></td>
</tr>
<tr>
<td>shadow</td>
<td>忽略重复声明变量产生的告警</td>
<td></td>
</tr>
<tr>
<td>sub</td>
<td>忽略使用[]而不是.访问属性的告警</td>
<td></td>
</tr>
<tr>
<td>supernew</td>
<td>忽略使用诡异的构造函数告警</td>
<td><a href="http://www.jshint.com/docs/options/#supernew" target="_blank" rel="noopener">示例</a></td>
</tr>
<tr>
<td>validthis</td>
<td>忽略严格模式在非构造函数中使用this</td>
<td>函数内部使用</td>
</tr>
<tr>
<td>noyield</td>
<td>允许没有使用yield的generator function</td>
<td></td>
</tr>
</tbody>
</table>
<h3 id="环境配置-Environments"><a href="#环境配置-Environments" class="headerlink" title="环境配置(Environments)"></a>环境配置(Environments)</h3><table>
<thead>
<tr>
<th>配置项</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>browser</td>
<td>可以使用HTML5的新特性如FileReader等</td>
</tr>
<tr>
<td>couch</td>
<td>CouchDB</td>
</tr>
<tr>
<td>devel</td>
<td>可以使用穷人版的debug工具，比如console、alert</td>
</tr>
<tr>
<td>dojo</td>
<td>Dojo Toolkit</td>
</tr>
<tr>
<td>jquery</td>
<td>jquery</td>
</tr>
<tr>
<td>node</td>
<td>Node.js</td>
</tr>
<tr>
<td>nonstandard</td>
<td>允许流行而不标准的全局变量，比如escape和unescape</td>
</tr>
<tr>
<td>phantom</td>
<td><a href="http://phantomjs.org/" target="_blank" rel="noopener">PhantomJS</a></td>
</tr>
<tr>
<td> protypejs</td>
<td>protypejs</td>
</tr>
<tr>
<td>rhino</td>
<td><a href="http://www.mozilla.org/rhino/" target="_blank" rel="noopener">Rhino</a></td>
</tr>
<tr>
<td>worker</td>
<td>表明你的脚本运行在<a href="https://developer.mozilla.org/en/Using_web_workers" target="_blank" rel="noopener">Web Worker</a>中</td>
</tr>
<tr>
<td>wsh</td>
<td><a href="http://en.wikipedia.org/wiki/Windows_Script_Host" target="_blank" rel="noopener">Windows_Script_Host</a></td>
</tr>
<tr>
<td>yui</td>
<td><a href="http://yuilibrary.com/" target="_blank" rel="noopener">YUI</a></td>
</tr>
</tbody>
</table>
<h2 id="币须网的JSHint"><a href="#币须网的JSHint" class="headerlink" title="币须网的JSHint"></a>币须网的JSHint</h2><p><a href="http://www.coinxu.com" target="_blank" rel="noopener">币须网</a>作为国内第一家应用<strong>比特币多重签名</strong>技术的电子商务平台，后台全部使用Node.js搭建，在团队协作上也需要JSHint这样的工具来进行检查和约束。我们正在使用如下的配置：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">	<span class="string">"bitwise"</span>: <span class="literal">true</span>,</span><br><span class="line">	<span class="string">"camelcase"</span>: <span class="literal">false</span>,</span><br><span class="line">	<span class="string">"curly"</span>: <span class="literal">true</span>,</span><br><span class="line">	<span class="string">"eqeqeq"</span>: <span class="literal">true</span>,</span><br><span class="line">	<span class="string">"es3"</span>: <span class="literal">false</span>,</span><br><span class="line">	<span class="string">"forin"</span>: <span class="literal">true</span>,</span><br><span class="line">	<span class="string">"freeze"</span>: <span class="literal">false</span>,</span><br><span class="line">	<span class="string">"immed"</span>: <span class="literal">false</span>,</span><br><span class="line">	<span class="string">"indent"</span>: <span class="number">2</span>,</span><br><span class="line">	<span class="string">"latedef"</span>: <span class="literal">true</span>,</span><br><span class="line">	<span class="string">"newcap"</span>: <span class="literal">true</span>,</span><br><span class="line">	<span class="string">"noarg"</span>: <span class="literal">true</span>,</span><br><span class="line">	<span class="string">"noempty"</span>: <span class="literal">true</span>,</span><br><span class="line">	<span class="string">"nonbsp"</span>: <span class="literal">true</span>,</span><br><span class="line">	<span class="string">"nonew"</span>: <span class="literal">true</span>,</span><br><span class="line">	<span class="string">"plusplus"</span>: <span class="literal">true</span>,</span><br><span class="line">	<span class="string">"quotmark"</span>: <span class="literal">true</span>,</span><br><span class="line">	<span class="string">"undef"</span>: <span class="literal">true</span>,</span><br><span class="line">	<span class="string">"unused"</span>: <span class="literal">true</span>,</span><br><span class="line">	<span class="string">"strict"</span>: <span class="literal">false</span>,</span><br><span class="line">	<span class="string">"maxparams"</span>: <span class="number">5</span>,</span><br><span class="line">	<span class="string">"maxdepth"</span>: <span class="number">8</span>,</span><br><span class="line">	<span class="string">"maxstatements"</span>: <span class="number">50</span>,</span><br><span class="line">	<span class="string">"maxcomplexity"</span>:<span class="literal">false</span>,</span><br><span class="line"></span><br><span class="line">	<span class="string">"asi"</span>: <span class="literal">true</span>,</span><br><span class="line">	<span class="string">"boss"</span>: <span class="literal">false</span>,</span><br><span class="line">	<span class="string">"debug"</span>: <span class="literal">false</span>,</span><br><span class="line">	<span class="string">"eqnull"</span>: <span class="literal">true</span>,</span><br><span class="line">	<span class="string">"esnext"</span>: <span class="literal">true</span>,</span><br><span class="line">	<span class="string">"evil"</span>: <span class="literal">true</span>,</span><br><span class="line">	<span class="string">"expr"</span>: <span class="literal">false</span>,</span><br><span class="line">	<span class="string">"funcscope"</span>: <span class="literal">false</span>,</span><br><span class="line">	<span class="string">"globalstrict"</span>: <span class="literal">true</span>,</span><br><span class="line">	<span class="string">"iterator"</span>: <span class="literal">true</span>,</span><br><span class="line">	<span class="string">"lastsemic"</span>: <span class="literal">true</span>,</span><br><span class="line">	<span class="string">"laxbreak"</span>: <span class="literal">false</span>,</span><br><span class="line">	<span class="string">"laxcomma"</span>: <span class="literal">false</span>,</span><br><span class="line">	<span class="string">"loopfunc"</span>: <span class="literal">false</span>,</span><br><span class="line">	<span class="string">"maxerr"</span>: <span class="number">50</span>,</span><br><span class="line">	<span class="string">"multistr"</span>: <span class="literal">false</span>,</span><br><span class="line">	<span class="string">"notypeof"</span>: <span class="literal">false</span>,</span><br><span class="line">	<span class="string">"proto"</span>:<span class="literal">true</span>,</span><br><span class="line">	<span class="string">"shadow"</span>: <span class="literal">false</span>,</span><br><span class="line">	<span class="string">"supernew"</span>: <span class="literal">false</span>,</span><br><span class="line">	<span class="string">"validthis"</span>: <span class="literal">false</span>,</span><br><span class="line">	<span class="string">"noyield"</span>: <span class="literal">false</span>,</span><br><span class="line"></span><br><span class="line">	<span class="string">"node"</span>: <span class="literal">true</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p><strong>强迫症</strong>每个人或多或少都有一点，沉溺于习惯求稳而不求变也是人之本性。但如果要突破自我，不求变通怕是成不了气候。再则善于运用标准化的工具也算得上一大进步，程序员尤其如此，如果偶然之间随手解救了团队和自己也算得上造福一方吧。</p>

      
    </div>
    <footer class="article-footer">
      <a data-url="http://yoursite.com/2014/07/29/程序员的强迫症/" data-id="cjf865ug4001q8wp1ow9ef2r4" class="article-share-link">Share</a>
      
      
  <ul class="article-tag-list"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/JavaScript/">JavaScript</a></li></ul>

    </footer>
  </div>
  
</article>


  
    <article id="post-初识JavaScript Promises之二" class="article article-type-post" itemscope itemprop="blogPost">
  <div class="article-meta">
    <a href="/2014/07/15/初识JavaScript Promises之二/" class="article-date">
  <time datetime="2014-07-14T16:00:00.000Z" itemprop="datePublished">2014-07-15</time>
</a>
    
  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/2014/07/15/初识JavaScript Promises之二/">初识JavaScript Promises之二</a>
    </h1>
  

      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <p>上一篇我们初步学习了JavaScript Promises，本篇将介绍Promise如何优雅地进行错误处理以及提升操作<strong>node.js风格</strong>[^1]的异步方法的逼格，没错就是使用<strong>promisify</strong>[^2]。</p>
<h2 id="异步编程中的错误处理"><a href="#异步编程中的错误处理" class="headerlink" title="异步编程中的错误处理"></a>异步编程中的错误处理</h2><p> 人性的、理想的也正如很多编程语言中已经实现的错误处理方式应该是这样：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="keyword">var</span> val = <span class="built_in">JSON</span>.parse(fs.readFileSync(<span class="string">"file.json"</span>));</span><br><span class="line">&#125;<span class="keyword">catch</span>(<span class="built_in">SyntaxError</span> e) &#123;<span class="comment">//json语法错误</span></span><br><span class="line">    <span class="built_in">console</span>.error(<span class="string">"不符合json格式"</span>);</span><br><span class="line">&#125;<span class="keyword">catch</span>(<span class="built_in">Error</span> e) &#123;<span class="comment">//其它类型错误</span></span><br><span class="line">    <span class="built_in">console</span>.error(<span class="string">"无法读取文件"</span>)</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>很遗憾，JavaScript并不支持上述方式，于是“聪明的猴子”很可能写出下面的代码：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="comment">//code</span></span><br><span class="line">&#125;<span class="keyword">catch</span>(e) &#123;</span><br><span class="line">    <span class="keyword">if</span>( e <span class="keyword">instanceof</span> <span class="built_in">SyntaxError</span>) &#123;</span><br><span class="line">        <span class="comment">//handle</span></span><br><span class="line">    &#125;<span class="keyword">else</span> &#123;</span><br><span class="line">      	<span class="comment">//handle  </span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>相信没人会喜欢第二段代码，不过传统的JavaScript也只能帮你到这里了。</p>
<p>上面的代码是同步模式，异步模式中的错误处理又是如何呢？</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">fs.readFile(<span class="string">'file.json'</span>, <span class="string">'utf8'</span>, <span class="function"><span class="keyword">function</span>(<span class="params">err, data</span>)</span>&#123;</span><br><span class="line">	<span class="keyword">if</span>(err)&#123;</span><br><span class="line">		<span class="built_in">console</span>.error(<span class="string">"无法读取文件"</span>)</span><br><span class="line">	&#125;<span class="keyword">else</span>&#123;</span><br><span class="line">		<span class="keyword">try</span>&#123;</span><br><span class="line">			<span class="keyword">var</span> json = <span class="built_in">JSON</span>.parese(data)</span><br><span class="line">		&#125;<span class="keyword">catch</span>(e)&#123;</span><br><span class="line">			<span class="built_in">console</span>.error(<span class="string">"不符合json格式"</span>);</span><br><span class="line">		&#125;</span><br><span class="line">	&#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>
<p>友情提醒：在node.js中你应该尽量避免使用同步方法。</p>
<p>仔细比较第一段和第三段的代码的差异会发现，如此简单的代码竟然用了三次缩进！如果再加入其它异步操作，邂逅<code>callback hell</code>是必然的了。</p>
<hr>
<h2 id="使用Promise进行错误处理"><a href="#使用Promise进行错误处理" class="headerlink" title="使用Promise进行错误处理"></a>使用Promise进行错误处理</h2><p>假设fs.readFileAsync是fs.readFile的Promise版本，这意味着什么呢，不妨回忆一下：</p>
<blockquote>
<ul>
<li>fs.readFileAsync方法的返回结果是一个Promise对象</li>
<li>fs.readFileAsync方法的返回结果拥有一个then方法</li>
<li>fs.readFileAsync方法接受参数与fs.readFile一致，除了最后一个回调函数</li>
</ul>
</blockquote>
<p>返回Promise对象意味着，执行fs.readFileAsync并不会立即执行异步操作，而是通过调用其then方法来执行，then方法接受的回调函数用于处理正确返回结果。所以使用fs.readFileAsync的使用方式如下：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//Promise版本</span></span><br><span class="line">fs.readFileAsync(<span class="string">'file.json'</span>, <span class="string">'utf8'</span>).then(<span class="function"><span class="keyword">function</span>(<span class="params">data</span>)</span>&#123;</span><br><span class="line">	<span class="built_in">console</span>.log(data)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>
<p>OK，让我们继续错误处理这个话题。由于<a href="http://promisesaplus.com/" target="_blank" rel="noopener">Promises/A+</a>标准对Promise对象只规定了唯一的then方法，没有专门针对catch或者error的方法，我们将继续使用<a href="https://github.com/petkaantonov/bluebird" target="_blank" rel="noopener">bluebird</a>。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 带错误处理的Promise版本</span></span><br><span class="line">fs.readFileAsync(<span class="string">'file.json'</span>, <span class="string">'utf8'</span>).then(<span class="function"><span class="keyword">function</span>(<span class="params">data</span>)</span>&#123;</span><br><span class="line">	<span class="built_in">console</span>.log(data)</span><br><span class="line">&#125;).catch(<span class="built_in">SyntaxError</span>, <span class="function"><span class="keyword">function</span>(<span class="params">e</span>)</span>&#123;</span><br><span class="line">	<span class="comment">//code here</span></span><br><span class="line">&#125;).catch(<span class="function"><span class="keyword">function</span>(<span class="params">e</span>)</span>&#123;</span><br><span class="line">	<span class="comment">//code here</span></span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>
<p>上面的代码没有嵌套回调，和本文开始的第一段代码的编写模式也基本一致。</p>
<h2 id="神奇的Promisify"><a href="#神奇的Promisify" class="headerlink" title="神奇的Promisify"></a>神奇的Promisify</h2><p>注：</p>
<p>下面我们看如何对fs.readFileAsync方法进行promisify，依然是使用bluebird。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> <span class="built_in">Promise</span> = <span class="built_in">require</span>(<span class="string">'bluebird'</span>)</span><br><span class="line">fs.readFileAsync = <span class="built_in">Promise</span>.promisify(fs.readFie, fs)</span><br></pre></td></tr></table></figure>
<p>怎么样，就是如此简单！对于bluebird它还有一个更强大的方法，那就是promisify的高级版本 <code>promisifyAll</code>，比如：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> <span class="built_in">Promise</span> = <span class="built_in">require</span>(<span class="string">'bluebird'</span>)</span><br><span class="line"><span class="built_in">Promise</span>.promisifyAll(fs)</span><br></pre></td></tr></table></figure>
<p>执行完上面的代码之后，fs对象下所有的异步方法都会对应的生成一个Promise版本方法，比如fs.readFile对应fs.readFileAsync，fs.mkdir对应fs.mkdirAsync，以此类推。</p>
<p>另外要注意的就是，Promise版本的函数除了最后一个参数（回调函数），其它参数与原函数均一一对应，调用的时候别忘了传递原有的参数。</p>
<p>对fs的<code>promisification</code>还不能令我满足，我需要更神奇的魔法：</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// redis</span></span><br><span class="line"><span class="keyword">var</span> <span class="built_in">Promise</span> = <span class="built_in">require</span>(<span class="string">"bluebird"</span>);</span><br><span class="line"><span class="built_in">Promise</span>.promisifyAll(<span class="built_in">require</span>(<span class="string">"redis"</span>));</span><br><span class="line"></span><br><span class="line"><span class="comment">// mongoose</span></span><br><span class="line"><span class="keyword">var</span> <span class="built_in">Promise</span> = <span class="built_in">require</span>(<span class="string">"bluebird"</span>);</span><br><span class="line"><span class="built_in">Promise</span>.promisifyAll(<span class="built_in">require</span>(<span class="string">"mongoose"</span>));</span><br><span class="line"></span><br><span class="line"><span class="comment">// mongodb</span></span><br><span class="line"><span class="keyword">var</span> <span class="built_in">Promise</span> = <span class="built_in">require</span>(<span class="string">"bluebird"</span>);</span><br><span class="line"><span class="built_in">Promise</span>.promisifyAll(<span class="built_in">require</span>(<span class="string">"mongodb"</span>));</span><br><span class="line"></span><br><span class="line"><span class="comment">// mysql</span></span><br><span class="line"><span class="keyword">var</span> <span class="built_in">Promise</span> = <span class="built_in">require</span>(<span class="string">"bluebird"</span>);</span><br><span class="line"><span class="built_in">Promise</span>.promisifyAll(<span class="built_in">require</span>(<span class="string">"mysql/lib/Connection"</span>).prototype);</span><br><span class="line"><span class="built_in">Promise</span>.promisifyAll(<span class="built_in">require</span>(<span class="string">"mysql/lib/Pool"</span>).prototype);</span><br><span class="line"></span><br><span class="line"><span class="comment">// request</span></span><br><span class="line"><span class="keyword">var</span> <span class="built_in">Promise</span> = <span class="built_in">require</span>(<span class="string">"bluebird"</span>);</span><br><span class="line"><span class="built_in">Promise</span>.promisifyAll(<span class="built_in">require</span>(<span class="string">"request"</span>));</span><br><span class="line"></span><br><span class="line"><span class="comment">// mkdir</span></span><br><span class="line"><span class="keyword">var</span> <span class="built_in">Promise</span> = <span class="built_in">require</span>(<span class="string">"bluebird"</span>);</span><br><span class="line"><span class="built_in">Promise</span>.promisifyAll(<span class="built_in">require</span>(<span class="string">"mkdirp"</span>));</span><br><span class="line"></span><br><span class="line"><span class="comment">// winston</span></span><br><span class="line"><span class="keyword">var</span> <span class="built_in">Promise</span> = <span class="built_in">require</span>(<span class="string">"bluebird"</span>);</span><br><span class="line"><span class="built_in">Promise</span>.promisifyAll(<span class="built_in">require</span>(<span class="string">"winston"</span>));</span><br><span class="line"></span><br><span class="line"><span class="comment">// Nodemailer</span></span><br><span class="line"><span class="keyword">var</span> <span class="built_in">Promise</span> = <span class="built_in">require</span>(<span class="string">"bluebird"</span>);</span><br><span class="line"><span class="built_in">Promise</span>.promisifyAll(<span class="built_in">require</span>(<span class="string">"nodemailer"</span>));</span><br><span class="line"></span><br><span class="line"><span class="comment">// pg</span></span><br><span class="line"><span class="keyword">var</span> <span class="built_in">Promise</span> = <span class="built_in">require</span>(<span class="string">"bluebird"</span>);</span><br><span class="line"><span class="built_in">Promise</span>.promisifyAll(<span class="built_in">require</span>(<span class="string">"pg"</span>));</span><br><span class="line"></span><br><span class="line"><span class="comment">// ...</span></span><br></pre></td></tr></table></figure>
<p>少年，这下你颤抖了吗？</p>
<p>注：如果你正在使用mongoose，除了bluebird你可能还需要<a href="https://github.com/simongfxu/mongoomise" target="_blank" rel="noopener"><strong>mongoomise</strong></a>，它的优点在于：</p>
<blockquote>
<ul>
<li>能够接受任意的Promise Library (Q/when.js/RSVP/bluebird/es6-promise等等)</li>
<li>支持对多个数据库实例进行promisify</li>
<li>能够对Model自定义静态私有方法进行promisify，而bluebird.promisifyAll不支持</li>
<li>mongoomise + bluebird与仅使用bluebird性能相差无几，可能更好。</li>
</ul>
</blockquote>
<p>我们<a href="http://www.coinxu.com" target="_blank" rel="noopener"><strong>币须网</strong></a>已经在生产环境中使用mongoomise + bluebird，目前为止一切安好。</p>
<p>注：</p>
<ul>
<li><p>node.js风格函数指的是这样的一种异步函数，它接受的最后一个参数是异步操作完成之后的回调函数，这个回调函数的第一个参数接受执行错误的Error对象，第二个参数接受成功返回值）。</p>
</li>
<li><p><code>promisify</code>大概的意思就是根据一个<strong>node.js风格</strong>的异步方法生成另一个等价的Promise风格的方法（这个方法返回值是一个Promise，其它形参与原方法相同除了没有最后一个回调函数），这个名词我最早是看到bluebird使用。</p>
</li>
</ul>
<p>(未完待续 2014-07-15 23:40)</p>

      
    </div>
    <footer class="article-footer">
      <a data-url="http://yoursite.com/2014/07/15/初识JavaScript Promises之二/" data-id="cjf865ug1001l8wp1u302v3bb" class="article-share-link">Share</a>
      
      
  <ul class="article-tag-list"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/JavaScript/">JavaScript</a></li></ul>

    </footer>
  </div>
  
</article>


  
    <article id="post-初识JavaScript Promises之一" class="article article-type-post" itemscope itemprop="blogPost">
  <div class="article-meta">
    <a href="/2014/06/28/初识JavaScript Promises之一/" class="article-date">
  <time datetime="2014-06-27T16:00:00.000Z" itemprop="datePublished">2014-06-28</time>
</a>
    
  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/2014/06/28/初识JavaScript Promises之一/">初识JavaScript Promises之一</a>
    </h1>
  

      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <p>JavaScript有很多槽点，嵌套回调怕是千夫所指。</p>
<p>很久之前，我一直使用async来处理JavaScript异步编程中的嵌套回调问题。当然我也大概的了解过一些其它旨在解决这些问题的类库，诸如EventProxy、Jscex、StepJS、thenjs。</p>
<p>当我第一次看到Promises规范的时候，我根本无法理解它所带来的好处。譬如每个初次学习Promises的人都见过如下的示例代码：<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//callbacks</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">callback</span>(<span class="params">err, value</span>)</span>&#123;</span><br><span class="line">	<span class="keyword">if</span>(err)&#123;</span><br><span class="line">		<span class="comment">// do something</span></span><br><span class="line">		<span class="keyword">return</span>;</span><br><span class="line">	&#125;</span><br><span class="line">	<span class="comment">//do other things with value</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">//Promises</span></span><br><span class="line">promise.then(<span class="function"><span class="keyword">function</span>(<span class="params">value</span>)</span>&#123;</span><br><span class="line">	<span class="comment">//do something with value</span></span><br><span class="line">&#125;, <span class="function"><span class="keyword">function</span>(<span class="params">err</span>)</span>&#123;</span><br><span class="line">	<span class="comment">//do other things with error</span></span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure></p>
<p>很难相信上面的代码会让人对Promises刮目相看。不过正如<a href="https://github.com/petkaantonov/bluebird" target="_blank" rel="noopener">bluebird</a>作者Petka所说，上面的代码是<br><a href="https://twitter.com/PetkaAntonov/status/475274392461910016" target="_blank" rel="noopener">“最不诚实的比较”</a>。所以我恳请你把类似的代码从你的记忆中擦出吧。</p>
<p>不妨让我们再回到async的讨论上。async的问题在于它不能优雅地应对需求的变化，一旦业务逻辑有较大的变化，代码结构会进行大幅度的调整，而Promises却能够轻松的应对这种变化。待时机适宜我会进行详细的比较，首先让我们开始快速地了解Promises。</p>
<hr>
<h2 id="Promises是什么"><a href="#Promises是什么" class="headerlink" title="Promises是什么"></a>Promises是什么</h2><p>Promises象征着一个异步操作的最终结果。Promises交互主要通过它的then方法，then方法接受一个回调函数，这个回调函数接受执行成功的返回值或执行失败的错误原因，错误原因一般是Error对象。<strong>需要注意的是，then方法执行的返回值是一个Promise对象，而then方法接受的回调函数的返回值则可以是任意的JavaScript对象，包括Promises。基于这种机制，Promise对象的链式调用就起作用了。</strong></p>
<h2 id="Promises的状态"><a href="#Promises的状态" class="headerlink" title="Promises的状态"></a>Promises的状态</h2><p>Promise对象有三种状态：pending（初始状态）、fulfilled（成功执行）、rejected（执行出错）。pending状态的Promise对象可以转换到其它两种状态。</p>
<hr>
<p> 上面的文本不够形象，不妨上些代码来加深对Promises的认识。</p>
<p>注：由于主流的JavaScript环境（包括NodeJS）对Promises/A+标准的实现差强人意，我的示例均使用了第三方类库<a href="https://github.com/petkaantonov/bluebird" target="_blank" rel="noopener">bluebird</a>。</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> fs = <span class="built_in">require</span>(<span class="string">'fs'</span>)</span><br><span class="line"><span class="keyword">var</span> <span class="built_in">Promise</span> = <span class="built_in">require</span>(<span class="string">'bluebird'</span>)</span><br><span class="line"><span class="comment">//改造fs.readFile为Promise版本</span></span><br><span class="line"><span class="keyword">var</span> readFileAsync = <span class="function"><span class="keyword">function</span>(<span class="params">path</span>)</span>&#123;</span><br><span class="line">	<span class="comment">//返回一个Promise对象，初始状态pending</span></span><br><span class="line">	<span class="keyword">return</span> <span class="keyword">new</span> <span class="built_in">Promise</span>(<span class="function"><span class="keyword">function</span>(<span class="params">fulfill, reject</span>)</span>&#123;</span><br><span class="line">		fs.readFile(path,  <span class="string">'utf8'</span>, <span class="function"><span class="keyword">function</span>(<span class="params">err, content</span>)</span>&#123;</span><br><span class="line">			<span class="comment">//由pending状态进入rejected状态</span></span><br><span class="line">			<span class="keyword">if</span>(err)<span class="keyword">return</span> reject(err)</span><br><span class="line">			<span class="comment">//由pending状态进入fulfilled状态</span></span><br><span class="line">			<span class="keyword">return</span> fulfill(content)</span><br><span class="line">		&#125;)</span><br><span class="line">	&#125;)</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">//开始使用，调用其then方法，回调接受执行成功的返回值</span></span><br><span class="line">readFileAsync(<span class="string">'./promise-1.js'</span>).then(<span class="function"><span class="keyword">function</span>(<span class="params">content</span>)</span>&#123;</span><br><span class="line">	<span class="built_in">console</span>.log(content)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure>
<p>看了上面的代码以后，是不是觉得Promises其实并不复杂呢。</p>
<p>OK，我们继续延续上面的代码，来简单比较一下传统回调和Promises的使用上的差别：<br><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">* 简单比较一下传统方式和Promises方式</span></span><br><span class="line"><span class="comment">* 需求：读取两个文件并打印内容</span></span><br><span class="line"><span class="comment">* */</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">//callbacks</span></span><br><span class="line">fs.readFile(<span class="string">'./promise-1.js'</span>, <span class="string">'utf8'</span>, <span class="function"><span class="keyword">function</span>(<span class="params">err, content1</span>)</span>&#123;</span><br><span class="line">	<span class="comment">//嵌套一次</span></span><br><span class="line">	<span class="built_in">console</span>.log(<span class="string">'#'</span>, content1)</span><br><span class="line">	fs.readFile(<span class="string">'./promise-1.js'</span>, <span class="string">'utf8'</span>, <span class="function"><span class="keyword">function</span>(<span class="params">err, content2</span>)</span>&#123;</span><br><span class="line"> 		<span class="comment">//第二次嵌套</span></span><br><span class="line">		<span class="built_in">console</span>.log(<span class="string">'##'</span>, content2)</span><br><span class="line">	&#125;)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="comment">//Promises</span></span><br><span class="line">readFileAsync(<span class="string">'./promise-1.js'</span>).then(<span class="function"><span class="keyword">function</span>(<span class="params">content1</span>)</span>&#123;</span><br><span class="line">	<span class="built_in">console</span>.log(<span class="string">'#'</span>, content1)</span><br><span class="line">	<span class="comment">//这里返回一个Promise对象</span></span><br><span class="line">	<span class="keyword">return</span> readFileAsync(<span class="string">'./promiscuitye-1.js'</span>)</span><br><span class="line">&#125;).then(<span class="function"><span class="keyword">function</span>(<span class="params">content2</span>)</span>&#123;</span><br><span class="line">	<span class="built_in">console</span>.log(<span class="string">'##'</span>, content2)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure></p>
<p>上面的代码都没有错误处理，这是一个后果很严重的坏习惯。不过今天我们的重点不在这里，而是分析上下两段代码的主要区别。</p>
<p>第一段代码是传统的嵌套回调，在第二次打印的时候已经使用了两次缩进，而Promises链式调用then方法成功地避免了一次缩进（嵌套），维持了代码结构的相对平坦。上面的代码略显简陋，如果再加上错误处理，Promises毫无疑问将会大放光彩，有兴趣请关注后续章节。</p>
<p>本章写到这里就结束了，相信大家已经对Promises的有了一个初步认识。规范文档往往很难理解，我没有过多的描述规范，因为我相信代码最能够解释一切。不过对规范文档有兴趣的可以自行阅读参考链接。</p>
<p>最后我想强调的一点就是：<strong>Promises这种维持代码结构平坦的魔力在业务逻辑复杂多变的情况下是非常有用的</strong>。</p>
<h2 id="参考链接"><a href="#参考链接" class="headerlink" title="参考链接"></a>参考链接</h2><blockquote>
<ul>
<li><a href="https://github.com/promises-aplus/promises-spec" target="_blank" rel="noopener">Promises/A+ 标准</a></li>
<li><a href="https://github.com/petkaantonov/bluebird" target="_blank" rel="noopener">Bluebird</a></li>
</ul>
</blockquote>
<hr>
<p>未完待续(2014-06-28 00:59)</p>

      
    </div>
    <footer class="article-footer">
      <a data-url="http://yoursite.com/2014/06/28/初识JavaScript Promises之一/" data-id="cjf865ug0001j8wp1iubqx36w" class="article-share-link">Share</a>
      
      
  <ul class="article-tag-list"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/JavaScript/">JavaScript</a></li></ul>

    </footer>
  </div>
  
</article>


  
    <article id="post-Ajax-Encoding" class="article article-type-post" itemscope itemprop="blogPost">
  <div class="article-meta">
    <a href="/2012/11/11/Ajax-Encoding/" class="article-date">
  <time datetime="2012-11-10T16:00:00.000Z" itemprop="datePublished">2012-11-11</time>
</a>
    
  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/2012/11/11/Ajax-Encoding/">Ajax编码问题</a>
    </h1>
  

      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <p>前些天看到一个关于Ajax编码的问题，当时被提问者的描述绕的都想不清楚，今天闲来没事就整理下。原文地址在<a href="http://segmentfault.com/q/1010000000130593" target="_blank" rel="noopener">这里</a>。</p>
<h2 id="问题描述"><a href="#问题描述" class="headerlink" title="问题描述"></a>问题描述</h2><p>我的前端页面为GBK，所以待发送的数据肯定也为GBK，而由ajax的特性其在发送前其会被自动转换为utf-8<br>所以后台接收到的数据为utf-8的<br>然后我的后台页面编码为UTF-8，同时设置了response header中的编码参数也为UTF-8，那么前端收到的数据应该也是UTF-8<br>但前端页面为GBK，理论上来说这样会产生乱码，可是并没有，这是为什么呢？</p>
<p>提问者的描述有点混乱，而且各种绕，一不小心就会踩到提问者设的坑。</p>
<h2 id="如何解决"><a href="#如何解决" class="headerlink" title="如何解决"></a>如何解决</h2><p>彻底解决这个问题你首先需要了解以下几个知识点：</p>
<blockquote>
<ul>
<li>页面表单提交到后台的数据编码与页面编码一致</li>
<li>通过javascript提交到后台的数据都会被转换为utf-8格式</li>
</ul>
</blockquote>
<p>所以原则上这样理解，这个问题就差不多了：</p>
<pre><code>输入编码为UTF-8，后台以UTF-8方式输出，保证输入和输出一致那么自然就没有乱码问题。
</code></pre><p>要让别人相信自己，首先得自己相信自己，我们先不妨来几个测试。另外问题描述既然提到了后台的文本编码，我们也需要重点测试一下。</p>
<h2 id="测试工作"><a href="#测试工作" class="headerlink" title="测试工作"></a>测试工作</h2><p>准备以下页面：</p>
<ul>
<li>主页面index.php</li>
<li>ajax后台页面utf8.php</li>
<li>ajax后台页面gbk.php</li>
</ul>
<h3 id="主页面-index-php，文本编码gbk"><a href="#主页面-index-php，文本编码gbk" class="headerlink" title="主页面 index.php，文本编码gbk"></a>主页面 index.php，文本编码gbk</h3><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!doctype html&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span>&gt;</span></span><br><span class="line">	<span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">		<span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">"gbk"</span>/&gt;</span></span><br><span class="line">		<span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">"http://s.segmentfault.com/js/jquery.js?12.11.5.1"</span>&gt;</span><span class="undefined"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">	<span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line">	<span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">		<span class="tag">&lt;<span class="name">button</span> <span class="attr">type</span>=<span class="string">"button"</span> <span class="attr">onclick</span>=<span class="string">"ajax_send('utf8')"</span>&gt;</span>JS提交(utf-8)<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line">		<span class="tag">&lt;<span class="name">button</span> <span class="attr">type</span>=<span class="string">"button"</span> <span class="attr">onclick</span>=<span class="string">"ajax_send('gbk')"</span>&gt;</span>JS提交(gbk)<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line">		<span class="tag">&lt;<span class="name">script</span> <span class="attr">type</span>=<span class="string">"text/javascript"</span>&gt;</span><span class="undefined"></span></span><br><span class="line"><span class="javascript">			<span class="keyword">var</span> ajax_send = <span class="function"><span class="keyword">function</span>(<span class="params">encoding</span>)</span>&#123;</span></span><br><span class="line"><span class="javascript">				<span class="keyword">var</span> xhr = <span class="keyword">new</span> XMLHttpRequest(), params = <span class="built_in">encodeURI</span>(<span class="string">'t=编码'</span>)</span></span><br><span class="line"><span class="javascript">				xhr.onreadystatechange = <span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123;</span></span><br><span class="line"><span class="javascript">					<span class="keyword">if</span>(<span class="keyword">this</span>.readyState == <span class="number">4</span> &amp;&amp; <span class="keyword">this</span>.status ==<span class="number">200</span>)</span></span><br><span class="line"><span class="javascript">						alert(<span class="keyword">this</span>.responseText)</span></span><br><span class="line"><span class="undefined">				&#125;</span></span><br><span class="line"><span class="javascript">				xhr.open(<span class="string">'POST'</span>,encoding + <span class="string">'.php'</span>,<span class="literal">true</span>)</span></span><br><span class="line"><span class="javascript">				xhr.setRequestHeader(<span class="string">"Content-type"</span>, <span class="string">"application/x-www-form-urlencoded"</span>);</span></span><br><span class="line"><span class="undefined">				xhr.send(params)</span></span><br><span class="line"><span class="undefined">			&#125;</span></span><br><span class="line"><span class="undefined">		</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">	<span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br></pre></td></tr></table></figure>
<h3 id="utf8-php，文本编码utf-8"><a href="#utf8-php，文本编码utf-8" class="headerlink" title="utf8.php，文本编码utf-8"></a>utf8.php，文本编码utf-8</h3><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?php</span></span><br><span class="line">header(<span class="string">'content-type:text/html;charset=utf-8'</span>);</span><br><span class="line"><span class="keyword">echo</span> $_POST[<span class="string">'t'</span>];</span><br><span class="line"><span class="meta">?&gt;</span></span><br></pre></td></tr></table></figure>
<h3 id="gbk-php，与utf8-php内容相同，区别就是文本编码为gbk"><a href="#gbk-php，与utf8-php内容相同，区别就是文本编码为gbk" class="headerlink" title="gbk.php，与utf8.php内容相同，区别就是文本编码为gbk"></a>gbk.php，与utf8.php内容相同，区别就是文本编码为gbk</h3><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?php</span></span><br><span class="line">header(<span class="string">'content-type:text/html;charset=utf-8'</span>);</span><br><span class="line"><span class="keyword">echo</span> $_POST[<span class="string">'t'</span>];</span><br><span class="line"><span class="meta">?&gt;</span></span><br></pre></td></tr></table></figure>
<p>启动Web Server，打开页面，分别点击按钮，都正确的弹出了“编码”二字，没有发现乱码现象。</p>
<h2 id="结论"><a href="#结论" class="headerlink" title="结论"></a>结论</h2><p>对于页面通过JS提交的数据：</p>
<blockquote>
<ul>
<li>后台接口，只要保证输入输出的编码一致，提交的数据就会有乱码产生</li>
<li>后台文件的文本编码以及前端页面的编码不会导致提交的数据返回时乱码，只要输入输出编码一致</li>
</ul>
</blockquote>
<p>对于以上两个结论，读者还可以这样测试。</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;?php</span></span><br><span class="line">header(<span class="string">'content-type:text/html;charset=gbk'</span>);</span><br><span class="line"><span class="keyword">echo</span> iconv(<span class="string">"utf-8"</span>,<span class="string">"gbk"</span>,$_POST[<span class="string">'t'</span>]);</span><br><span class="line"><span class="meta">?&gt;</span></span><br></pre></td></tr></table></figure>
<p>先将输入转码为gbk，然后输出同时设置为页面编码，这样也不会有乱码产生，但是服务端多了一道转码处理，效率不及直接输出utf-8。</p>
<p>对于后台附加的一些输出，比如在gbk.php中最后加一行</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">echo</span> <span class="string">"中文"</span>;</span><br></pre></td></tr></table></figure>
<p>这种情况会不会导致乱码呢？读者可以自己测试一下。</p>
<h2 id="深入"><a href="#深入" class="headerlink" title="深入"></a>深入</h2><p>稍微搞过前端的人都知道，使用script标签引入脚本的时候，有一个属性是charset用于指定脚本文件的编码。当页面编码与引入脚本的文本编码不一致的时候需要显式指定，否则就会容易造成乱码。</p>
<p>现在我们仔细对比这两种场景的处理方式，不就是一模一样吗？只要保证输入输出的编码一致即可。唯一的区别就是一个是静态资源，一个是动态接口。</p>
<p>你有可能会问，当输入输出指定的编码不一致时是不是就一定是乱码呢，浏览器又该如何解析呢？</p>
<p>这个时候你就该来<a href="http://ued.taobao.com/blog/2011/08/26/encode-war/" target="_blank" rel="noopener">这里</a>看看了。</p>
<p>光棍节写博客，看来我是真光棍，真屌丝。</p>

      
    </div>
    <footer class="article-footer">
      <a data-url="http://yoursite.com/2012/11/11/Ajax-Encoding/" data-id="cjf865uez00038wp1vwenwitx" class="article-share-link">Share</a>
      
      
  <ul class="article-tag-list"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/JavaScript/">JavaScript</a></li></ul>

    </footer>
  </div>
  
</article>


  
    <article id="post-iOS-UIWebView-Class-Reference" class="article article-type-post" itemscope itemprop="blogPost">
  <div class="article-meta">
    <a href="/2012/10/30/iOS-UIWebView-Class-Reference/" class="article-date">
  <time datetime="2012-10-29T16:00:00.000Z" itemprop="datePublished">2012-10-30</time>
</a>
    
  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/2012/10/30/iOS-UIWebView-Class-Reference/">iOS UIWebView Class Reference</a>
    </h1>
  

      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <p><a href="https://developer.apple.com/library/ios/#documentation/UIKit/Reference/UIWebView_Class/Reference/Reference.html" target="_blank" rel="noopener">原文地址</a></p>
<h2 id="属性"><a href="#属性" class="headerlink" title="属性"></a>属性</h2><h3 id="allowsInlineMediaPlayback"><a href="#allowsInlineMediaPlayback" class="headerlink" title="allowsInlineMediaPlayback"></a>allowsInlineMediaPlayback</h3><p><strong style="color:red;">前端重点关注</strong></p>
<p>是否允许页内播放视频，默认值NO，使用原生的全屏控制。</p>
<p>使用页面播放需要设置此属性为YES，并且video 元素要加上 <code>webkit-playsinline</code>属性。</p>
<h3 id="canGoBack"><a href="#canGoBack" class="headerlink" title="canGoBack"></a>canGoBack</h3><p>是否可以后退，只读属性</p>
<h3 id="canGoForward"><a href="#canGoForward" class="headerlink" title="canGoForward"></a>canGoForward</h3><p>是否可以前进，只读属性</p>
<h3 id="dataDetectorTypes"><a href="#dataDetectorTypes" class="headerlink" title="dataDetectorTypes"></a>dataDetectorTypes</h3><p>在webview被转换为可点击的URL内容的数据类型。</p>
<p>使用此属性可以指定譬如<code>http链接</code>，<code>Email地址</code>，<code>电话号码</code>等内容将自动转换为可点击的链接。当点击以后，webview寻找相应的应用程序来处理。</p>
<h3 id="delegate"><a href="#delegate" class="headerlink" title="delegate"></a>delegate</h3><p>委托，用于回调通知页面的加载状态，比如已经打开、打开完成或打开错误等。</p>
<h3 id="keyboardDisplayRequiresUserAction"><a href="#keyboardDisplayRequiresUserAction" class="headerlink" title="keyboardDisplayRequiresUserAction"></a>keyboardDisplayRequiresUserAction</h3><p><strong style="color:red;">前端重点关注</strong></p>
<p>显示键盘是否一定需要用户动作，默认值为YES，也就是用户必须主动点击可输入的表单元素以后才会显示键盘。</p>
<p>设置为NO以后，页面可以通过JS脚本的<code>focus</code>事件显示键盘。</p>
<h3 id="loading"><a href="#loading" class="headerlink" title="loading"></a>loading</h3><p>webview是否还在加载，只读属性</p>
<h3 id="mediaPlaybackAllowsAirPlay"><a href="#mediaPlaybackAllowsAirPlay" class="headerlink" title="#mediaPlaybackAllowsAirPlay"></a>#mediaPlaybackAllowsAirPlay</h3><p><strong style="color:red;">前端重点关注</strong></p>
<p>媒体播放是否允许<code>Air Play</code>???默认值为YES</p>
<h3 id="mediaPlaybackRequiresUserAction"><a href="#mediaPlaybackRequiresUserAction" class="headerlink" title="mediaPlaybackRequiresUserAction"></a>mediaPlaybackRequiresUserAction</h3><p><strong style="color:red;">前端重点关注</strong></p>
<p>媒体播放是否需要用户动作主动触发，默认值为YES。也就是说默认情况无法自动播放音频和视频。</p>
<p>那么默认设置下是不是一定无法自动播放呢？StackOverflow上找到一个<a href="http://stackoverflow.com/questions/4259928/how-can-i-autoplay-media-in-ios-4-2-1-mobile-safari" target="_blank" rel="noopener">方法</a>解决此问题（没有测试）</p>
<figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> ifr = <span class="built_in">document</span>.createElement(<span class="string">"iframe"</span>);</span><br><span class="line">ifr.setAttribute(<span class="string">'src'</span>, <span class="string">"http://mysite.com/myvideo.mp4"</span>);</span><br><span class="line">ifr.setAttribute(<span class="string">'width'</span>, <span class="string">'1px'</span>);</span><br><span class="line">ifr.setAttribute(<span class="string">'height'</span>, <span class="string">'1px'</span>);</span><br><span class="line">ifr.setAttribute(<span class="string">'scrolling'</span>, <span class="string">'no'</span>);</span><br><span class="line">ifr.style.border=<span class="string">"0px"</span>;</span><br><span class="line"><span class="built_in">document</span>.body.appendChild(ifr);</span><br></pre></td></tr></table></figure>
<h3 id="request"><a href="#request" class="headerlink" title="request"></a>request</h3><p>webview当前请求的URL，只读属性</p>
<h3 id="scalesPageToFit"><a href="#scalesPageToFit" class="headerlink" title="scalesPageToFit"></a>scalesPageToFit</h3><p>指定页面是否按比例缩放适应webview，并且用户可以更改缩放比例。默认值为NO，用户不能更改缩放比例。</p>
<h3 id="scrollView"><a href="#scrollView" class="headerlink" title="scrollView"></a>scrollView</h3><p>webview关联的scroll view，只读属性</p>
<h3 id="suppressesIncrementalRendering"><a href="#suppressesIncrementalRendering" class="headerlink" title="suppressesIncrementalRendering"></a>suppressesIncrementalRendering</h3><p>当页面完全加载到内存以后，webview是否禁止增量内容渲染，默认值为NO</p>
<p>iOS 6.0版本支持</p>
<p>##实例方法</p>
<h3 id="goBack"><a href="#goBack" class="headerlink" title="goBack"></a>goBack</h3><p>加载历史记录当前页之前的页面</p>
<h3 id="goForward"><a href="#goForward" class="headerlink" title="goForward"></a>goForward</h3><p>加载历史记录当前页之后的页面</p>
<h3 id="loadData-MIMEType-textEncodingName-baseURL"><a href="#loadData-MIMEType-textEncodingName-baseURL" class="headerlink" title="loadData:MIMEType:textEncodingName:baseURL"></a>loadData:MIMEType:textEncodingName:baseURL</h3><p>设置页面内容，MIMIE type，编码，URL</p>
<h3 id="loadHTMLString-baseURL"><a href="#loadHTMLString-baseURL" class="headerlink" title="loadHTMLString:baseURL:"></a>loadHTMLString:baseURL:</h3><p>设置页面内容</p>
<h3 id="loadRequest"><a href="#loadRequest" class="headerlink" title="loadRequest"></a>loadRequest</h3><p>根据指定的URL进行异步连接</p>
<h3 id="reload"><a href="#reload" class="headerlink" title="reload"></a>reload</h3><p>重新载入当前页</p>
<h3 id="stopLoading"><a href="#stopLoading" class="headerlink" title="stopLoading"></a>stopLoading</h3><p>取消当前页的加载</p>
<h3 id="stringByEvaluatingJavaScriptFromString"><a href="#stringByEvaluatingJavaScriptFromString" class="headerlink" title="stringByEvaluatingJavaScriptFromString"></a>stringByEvaluatingJavaScriptFromString</h3><p><strong>前端重点关注</strong></p>
<p>在页面加载完成以后运行JavaScript脚本，运行脚本有如下注意事项：</p>
<ul>
<li>脚本运行不得超过10秒</li>
<li>将要执行的脚本内存分配不得超过10M</li>
</ul>
<p><a href="http://url.cn/7Vf4bx" target="_blank" rel="noopener">这里</a>有一个比较详细的教程可以参考</p>

      
    </div>
    <footer class="article-footer">
      <a data-url="http://yoursite.com/2012/10/30/iOS-UIWebView-Class-Reference/" data-id="cjf865ufp000x8wp18zsm0pmk" class="article-share-link">Share</a>
      
      
  <ul class="article-tag-list"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/JavaScript/">JavaScript</a></li></ul>

    </footer>
  </div>
  
</article>


  
    <article id="post-A-Cross-Domain-Practice" class="article article-type-post" itemscope itemprop="blogPost">
  <div class="article-meta">
    <a href="/2012/07/27/A-Cross-Domain-Practice/" class="article-date">
  <time datetime="2012-07-26T16:00:00.000Z" itemprop="datePublished">2012-07-27</time>
</a>
    
  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 itemprop="name">
      <a class="article-title" href="/2012/07/27/A-Cross-Domain-Practice/">一次跨域实践</a>
    </h1>
  

      </header>
    
    <div class="article-entry" itemprop="articleBody">
      
        <p>项目需求:对一个照片列表页面的每张图片在前端进行下载耗时统计，并定期上报。</p>
<h2 id="基本思路"><a href="#基本思路" class="headerlink" title="基本思路"></a>基本思路</h2><p>使用<code>XMLHttpRequest</code>下载图片，实现下载耗时统计。上报功能则一次上报<code>localStorage</code>存储的多张图片的统计信息，这里不做讨论</p>
<h2 id="问题难点"><a href="#问题难点" class="headerlink" title="问题难点"></a>问题难点</h2><p>使用<code>XMLHttpRequest</code>需要跨域访问图片服务器</p>
<h2 id="其它说明"><a href="#其它说明" class="headerlink" title="其它说明"></a>其它说明</h2><ul>
<li><p>项目只服务于移动终端，本文的一切测试原则上只涉及<code>Webkit</code>系列的浏览器</p>
</li>
<li><p>为了方便快捷地看到效果，后台使用<code>Ruby</code>语言的轻量级框架<code>Sinatra</code></p>
</li>
<li><p>使用<code>127.0.0.1</code>访问<code>localhost</code>模拟跨域图片请求</p>
</li>
</ul>
<h2 id="项目文件"><a href="#项目文件" class="headerlink" title="项目文件"></a>项目文件</h2><ul>
<li><p>前端测试主页面 <code>test.html</code></p>
</li>
<li><p>后台请求处理文件 <code>myapp.rb</code></p>
</li>
<li><p>一张测试图片 <code>london.jpg</code>，大小47714B</p>
</li>
</ul>
<h2 id="代码预览"><a href="#代码预览" class="headerlink" title="代码预览"></a>代码预览</h2><h3 id="test-html"><a href="#test-html" class="headerlink" title="test.html"></a>test.html</h3><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!doctype html&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">"zh-CN"</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">"utf-8"</span> /&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">h1</span>&gt;</span>跨域获取图片的文件大小<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">button</span> <span class="attr">id</span>=<span class="string">"send"</span>&gt;</span>Send Ajax Request<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="undefined"></span></span><br><span class="line"><span class="javascript">        +<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123;</span></span><br><span class="line"><span class="javascript">            <span class="keyword">var</span> xhr = <span class="keyword">new</span> XMLHttpRequest(),</span></span><br><span class="line"><span class="javascript">                host = location.href.indexOf(<span class="string">'localhost'</span>)&gt;<span class="number">-1</span>?<span class="string">'127.0.0.1'</span>:<span class="string">'localhost'</span>,</span></span><br><span class="line"><span class="javascript">                url = <span class="string">'http://'</span> + host + <span class="string">':4567/img'</span></span></span><br><span class="line"><span class="javascript">            xhr.onload  = <span class="function"><span class="keyword">function</span>(<span class="params">e</span>)</span>&#123;</span></span><br><span class="line"><span class="javascript">                <span class="built_in">console</span>.log(e)</span></span><br><span class="line"><span class="undefined">            &#125;</span></span><br><span class="line"><span class="javascript">            xhr.onerror = <span class="function"><span class="keyword">function</span>(<span class="params">a</span>)</span>&#123;</span></span><br><span class="line"><span class="javascript">                <span class="built_in">console</span>.log(a)</span></span><br><span class="line"><span class="undefined">            &#125;</span></span><br><span class="line"><span class="javascript">            <span class="keyword">var</span> send = <span class="built_in">document</span>.getElementById(<span class="string">'send'</span>)</span></span><br><span class="line"><span class="javascript">            send.addEventListener(<span class="string">'click'</span>,<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123;</span></span><br><span class="line"><span class="undefined">                xhr.abort()</span></span><br><span class="line"><span class="javascript">                xhr.open(<span class="string">'GET'</span>, url, <span class="literal">true</span>)</span></span><br><span class="line"><span class="javascript">                xhr.send(<span class="literal">null</span>)</span></span><br><span class="line"><span class="javascript">            &#125;,<span class="literal">false</span>)</span></span><br><span class="line"><span class="undefined">        &#125;()</span></span><br><span class="line"><span class="undefined">    </span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure>
<h3 id="myapp-rb"><a href="#myapp-rb" class="headerlink" title="myapp.rb"></a>myapp.rb</h3><figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">require</span> <span class="string">'rubygems'</span></span><br><span class="line"><span class="keyword">require</span> <span class="string">'sinatra'</span></span><br><span class="line"></span><br><span class="line">get <span class="string">'/'</span> <span class="keyword">do</span></span><br><span class="line">    send_file <span class="string">'test.html'</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="comment">#跨域请求</span></span><br><span class="line">get <span class="string">'/img'</span> <span class="keyword">do</span></span><br><span class="line">    send_file <span class="string">'london.jpg'</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure>
<h2 id="开始工作"><a href="#开始工作" class="headerlink" title="开始工作"></a>开始工作</h2><p>启动web server以后就开始我们的测试工作了。打开<code>chrome</code>及其<code>控制台</code>，输入 <a href="http://localhost:4567/，点击页面中的按钮，不出意外你会在chrome控制台看到下面的错误提示。" target="_blank" rel="noopener">http://localhost:4567/，点击页面中的按钮，不出意外你会在chrome控制台看到下面的错误提示。</a></p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">XMLHttpRequest cannot load http://127.0.0.1:4567/img. Origin http://localhost:4567 is not allowed by Access-Control-Allow-Origin.</span><br></pre></td></tr></table></figure>
<p>因为我们对跨域访问没有进行任何设置，所以自然无法通过浏览器内在的<code>同源安全机制</code>。</p>
<h2 id="实现跨域访问"><a href="#实现跨域访问" class="headerlink" title="实现跨域访问"></a>实现跨域访问</h2><p>google如何实现跨域访问，很快找到了一种方法。在网站根目录放置<code>crossdomain.xml</code>的配置文件，就可以完美地实现跨域访问。难怪qq.com和taobao.com都不约而同的配置了这个文件，该文件的格式一般如下：</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">cross-domain-policy</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">allow-access-from</span> <span class="attr">domain</span>=<span class="string">"*.site.com"</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">allow-access-from</span> <span class="attr">domain</span>=<span class="string">"*.site.net"</span>/&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">allow-access-from</span> <span class="attr">domain</span>=<span class="string">"*.sitecdn.com"</span>/&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">cross-domain-policy</span>&gt;</span></span><br></pre></td></tr></table></figure>
<p>经过一番测试，发现这样还是无法实现跨域访问。后来终于知道crossdomain.xml文件是flash跨域专用，而且据说已经被Adobe公司申请为专利。看来这条路是走不通了。</p>
<p>于是继续google，终于在<a href="http://www.w3.org/TR/cors/" target="_blank" rel="noopener">这里</a>看到了一切关于我们想要的。，可惜<code>w3c</code>的文档总是那么地冗长而乏味，令人望而生畏。比如在介绍这个<code>Access-Control-Allow-Origin</code>的响应头如何设置时，看着这里的介绍是” origin-list-or-null | “<em>“，我以为可以一次性设置多个origin，比如<br>a.com,b.com,c.com。一番折腾之后发现这样设置和没有设置是一样的效果，如果不设置为星号就只能设置一个站点。那我们就暂时设置为”</em>“吧，安全问题稍后再考虑！修改我们的后台代码，加入一个全局的<code>过滤器</code>设置响应头，下面是修改后的myapp.rb文件：</p>
<figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># myapp.rb</span></span><br><span class="line"><span class="keyword">require</span> <span class="string">'rubygems'</span></span><br><span class="line"><span class="keyword">require</span> <span class="string">'sinatra'</span></span><br><span class="line"></span><br><span class="line">get <span class="string">'/'</span> <span class="keyword">do</span></span><br><span class="line">    send_file <span class="string">'test.html'</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="comment">#跨域请求</span></span><br><span class="line">get <span class="string">'/img'</span> <span class="keyword">do</span></span><br><span class="line">    send_file <span class="string">'london.jpg'</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line">after <span class="keyword">do</span></span><br><span class="line">    headers\</span><br><span class="line">    <span class="string">'Access-Control-Allow-Origin'</span> =&gt; <span class="string">'*'</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure>
<p>现在我们重启web server和浏览器，继续输入<a href="http://localhost:4567/，点击页面中的按钮发起跨域请求，查看crhome控制台，没有发现错误，请求顺利完成。那就让我们看看`Network`中的跨域访问请求到底多了些什么：" target="_blank" rel="noopener">http://localhost:4567/，点击页面中的按钮发起跨域请求，查看crhome控制台，没有发现错误，请求顺利完成。那就让我们看看`Network`中的跨域访问请求到底多了些什么：</a></p>
<ul>
<li><p>Access-Control-Allow-Origin:*</p>
</li>
<li><p>Connection:Keep-Alive</p>
</li>
<li><p>Content-Length:47714</p>
</li>
<li><p>Content-Type:image/jpeg</p>
</li>
<li><p>Date:Fri, 27 Jul 2012 07:11:42 GMT</p>
</li>
<li><p>Last-Modified:Fri, 27 Jul 2012 02:52:45 GMT</p>
</li>
<li><p>Server:WEBrick/1.3.1 (Ruby/1.8.7/2011-12-28)</p>
</li>
<li><p>X-Frame-Options:sameorigin</p>
</li>
<li><p>X-Xss-Protection:1; mode=block</p>
</li>
</ul>
<p>果然我们设置的响应头<code>Access-Control-Allow-Origin</code>生效了，太神奇了！通过服务器和浏览器的协作，我们轻松地实现了跨域访问。现在看来W3C搞的这些跨域访问控制的标准还是比较靠谱的啊，如果说有什么缺点，那就是设置多个站点的时候麻烦了点（比如qq.com需要配置跨域访问控制的话），其它的缺点一时我还真说不出来。</p>
<h2 id="读取图片的文件大小"><a href="#读取图片的文件大小" class="headerlink" title="读取图片的文件大小"></a>读取图片的文件大小</h2><p>目前为止，我们算是成功地实现了跨域请求，但是离我们的目标还差一点。我们还需要知道这个图片的文件大小，从之前服务器输出的响应头来看，应该就是<code>Content-Length</code>这个响应头了。那就修改下我们的页面代码，获取这个响应头吧：</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!doctype html&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">"zh-CN"</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">"utf-8"</span> /&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">h1</span>&gt;</span>跨域获取图片的文件大小<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">button</span> <span class="attr">id</span>=<span class="string">"send"</span>&gt;</span>Send Ajax Request<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="undefined"></span></span><br><span class="line"><span class="javascript">            +<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123;</span></span><br><span class="line"><span class="javascript">                <span class="keyword">var</span> xhr = <span class="keyword">new</span> XMLHttpRequest(),</span></span><br><span class="line"><span class="javascript">                    host = location.href.indexOf(<span class="string">'localhost'</span>)&gt;<span class="number">-1</span>?<span class="string">'127.0.0.1'</span>:<span class="string">'localhost'</span>,</span></span><br><span class="line"><span class="javascript">                    url = <span class="string">'http://'</span> + host + <span class="string">':4567/img'</span></span></span><br><span class="line"><span class="javascript">                xhr.onload  = <span class="function"><span class="keyword">function</span>(<span class="params">e</span>)</span>&#123;</span></span><br><span class="line"><span class="javascript">                    <span class="built_in">console</span>.log(<span class="keyword">this</span>.getResponseHeader(<span class="string">'Content-Length'</span>))</span></span><br><span class="line"><span class="javascript">                    <span class="built_in">console</span>.log(<span class="keyword">this</span>.responseText.length)</span></span><br><span class="line"><span class="undefined">                &#125;</span></span><br><span class="line"><span class="javascript">                xhr.onerror = <span class="function"><span class="keyword">function</span>(<span class="params">a</span>)</span>&#123;</span></span><br><span class="line"><span class="javascript">                    <span class="built_in">console</span>.log(a)</span></span><br><span class="line"><span class="undefined">                &#125;</span></span><br><span class="line"><span class="javascript">                <span class="keyword">var</span> send = <span class="built_in">document</span>.getElementById(<span class="string">'send'</span>)</span></span><br><span class="line"><span class="javascript">                send.addEventListener(<span class="string">'click'</span>,<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123;</span></span><br><span class="line"><span class="undefined">                    xhr.abort()</span></span><br><span class="line"><span class="javascript">                    xhr.open(<span class="string">'GET'</span>, url, <span class="literal">true</span>)</span></span><br><span class="line"><span class="javascript">                    xhr.send(<span class="literal">null</span>)</span></span><br><span class="line"><span class="javascript">                &#125;,<span class="literal">false</span>)</span></span><br><span class="line"><span class="undefined">            &#125;()</span></span><br><span class="line"><span class="undefined">        </span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure>
<p>刷新页面，打开控制台，点击按钮，一个红色的错误呈现在我们眼前：</p>
<p><code>Refused to get unsafe header &quot;Content-Length&quot;</code></p>
<p>怎么样让浏览器服软允许脚本获取这个响应头呢？还是继续去w3c的文档找找吧。很幸运，这个<code>Access-Control-Expose-Headers</code>似乎和我们的需求很匹配啊，不妨在过滤器中加入输出这个响应头来试一试：</p>
<figure class="highlight ruby"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># myapp.rb</span></span><br><span class="line"><span class="keyword">require</span> <span class="string">'rubygems'</span></span><br><span class="line"><span class="keyword">require</span> <span class="string">'sinatra'</span></span><br><span class="line"></span><br><span class="line">get <span class="string">'/'</span> <span class="keyword">do</span></span><br><span class="line">    send_file <span class="string">'test.html'</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line"><span class="comment">#跨域请求</span></span><br><span class="line">get <span class="string">'/img'</span> <span class="keyword">do</span></span><br><span class="line">    send_file <span class="string">'london.jpg'</span></span><br><span class="line"><span class="keyword">end</span></span><br><span class="line"></span><br><span class="line">after <span class="keyword">do</span></span><br><span class="line">    headers\</span><br><span class="line">    <span class="string">'Access-Control-Allow-Origin'</span> =&gt; <span class="string">'*'</span>,</span><br><span class="line">    <span class="string">'Access-Control-Expose-Headers'</span> =&gt; <span class="string">'Content-Length'</span></span><br><span class="line"><span class="keyword">end</span></span><br></pre></td></tr></table></figure>
<p>重启web server，刷新页面，打开chrome控制台，点击按钮。没有任何错误，顺利输出两个数值：47714，45926。看来已经能够顺利的获取到Content-Length响应头，而且数值也很准确，和文件大小一致。但是这个<code>responseText</code>为什么少了接近2KB呢？还请高人解答。</p>
<p>一切都很顺利，不过虽然只针对webkit系浏览器，至少也得测试下safari吧。打开safari及其控制台，输入url，点击按钮，居然抛出了和之前未设置<code>Access-Control-Expose-Headers</code>时一样的错误：</p>
<p><code>Refused to get unsafe header &quot;Content-Length&quot;</code></p>
<p>是不是浏览器缓存的问题呢？重启浏览器再测试，问题依旧。看来这应该就是safari的bug了。既然safari不支持，那就意味着我们前端脚本无法使用<code>getResponseHeader</code>了，那问题该如何解决呢？让我们再看看w3c上关于<code>XMLHttpRequest Level 2</code>的实现还有属性和函数我们可能用得上，继续找啊…终于发现XMLHttpRequest Level 2有一个<a href="http://www.w3.org/TR/XMLHttpRequest2/#handler-xhr-onprogress" target="_blank" rel="noopener">onprogress</a>事件和<a href="http://dvcs.w3.org/hg/progress/raw-file/tip/Overview.html#progressevent" target="_blank" rel="noopener">ProgressEvent</a>对象，于是修改我们的页面代码如下：</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">&lt;!doctype html&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">"zh-CN"</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">"utf-8"</span> /&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">h1</span>&gt;</span>跨域获取图片的文件大小<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">button</span> <span class="attr">id</span>=<span class="string">"send"</span>&gt;</span>Send Ajax Request<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="undefined"></span></span><br><span class="line"><span class="javascript">    +<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123;</span></span><br><span class="line"><span class="javascript">        <span class="keyword">var</span> xhr = <span class="keyword">new</span> XMLHttpRequest(),</span></span><br><span class="line"><span class="javascript">            host = location.href.indexOf(<span class="string">'localhost'</span>)&gt;<span class="number">-1</span>?<span class="string">'127.0.0.1'</span>:<span class="string">'localhost'</span>,</span></span><br><span class="line"><span class="javascript">            url = <span class="string">'http://'</span> + host + <span class="string">':4567/img'</span></span></span><br><span class="line"><span class="javascript">        xhr.onprogress  = <span class="function"><span class="keyword">function</span>(<span class="params">e</span>)</span>&#123;</span></span><br><span class="line"><span class="javascript">            <span class="keyword">if</span>(e.lengthComputable)&#123;</span></span><br><span class="line"><span class="javascript">                <span class="keyword">if</span>(e.loaded/e.total == <span class="number">1</span>)&#123;</span></span><br><span class="line"><span class="javascript">                    <span class="comment">//invoke onload</span></span></span><br><span class="line"><span class="javascript">                    <span class="built_in">console</span>.log(e.total)</span></span><br><span class="line"><span class="undefined">                &#125;</span></span><br><span class="line"><span class="undefined">            &#125;</span></span><br><span class="line"><span class="undefined">        &#125;</span></span><br><span class="line"><span class="javascript">        xhr.onerror = <span class="function"><span class="keyword">function</span>(<span class="params">a</span>)</span>&#123;</span></span><br><span class="line"><span class="javascript">            <span class="built_in">console</span>.log(a)</span></span><br><span class="line"><span class="undefined">        &#125;</span></span><br><span class="line"><span class="javascript">        <span class="keyword">var</span> send = <span class="built_in">document</span>.getElementById(<span class="string">'send'</span>)</span></span><br><span class="line"><span class="javascript">        send.addEventListener(<span class="string">'click'</span>,<span class="function"><span class="keyword">function</span>(<span class="params"></span>)</span>&#123;</span></span><br><span class="line"><span class="undefined">            xhr.abort()</span></span><br><span class="line"><span class="javascript">            xhr.open(<span class="string">'GET'</span>, url, <span class="literal">true</span>)</span></span><br><span class="line"><span class="javascript">            xhr.send(<span class="literal">null</span>)</span></span><br><span class="line"><span class="javascript">        &#125;,<span class="literal">false</span>)</span></span><br><span class="line"><span class="undefined">    &#125;()</span></span><br><span class="line"><span class="undefined"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br></pre></td></tr></table></figure>
<p>细心的同学可能会问为什么不在<code>onload</code>中执行相关操作，其实经我测试发现onload之后<code>e.total</code>和<code>e.loaded</code>已经被设置为0，虽然<code>responseText</code>依然可读，但是这与真实的文件大小有一点的误差。</p>
<h2 id="最后的完善"><a href="#最后的完善" class="headerlink" title="最后的完善"></a>最后的完善</h2><p>虽然我们的目标已经完成，但是我们的工作还没有结束。因为服务端存在一个严重的<code>安全隐患</code>。我们草率地将<code>Access-Control-Allow-Origin</code>设置为”＊“，不可避免地为一些黑客攻击创造了便利条件。</p>
<p>其实，既然我们的跨域访问只开放给部分信任的站点，那么我们只需要在后台的过滤器中判断请求来源是否属于白名单之中，如果存在则输出Access-Control-Allow-Origin响应头，内容为该站点域名。至于这个白名单的设计，可以参考<code>Apache</code>（之前一直误以为Apache的做法是业界的标准）。</p>
<p>另外跨域访问控制一般针对的是Ajax请求，所以我们还可以在过滤器中加入一个是否是Ajax请求的判断(服务端判断请求头中是否包含<code>X-Requested-With</code>:<code>XMLHttpRequest</code>)，避免为普通的请求也加入<code>Access-Control-Allow-Origin</code>这个响应头。</p>
<p>最后由于各浏览器对<code>Access-Control-Expose-Headers</code>的实现并不完善，所以这个响应头就显得异常鸡肋。</p>

      
    </div>
    <footer class="article-footer">
      <a data-url="http://yoursite.com/2012/07/27/A-Cross-Domain-Practice/" data-id="cjf865uev00018wp13hf6wp51" class="article-share-link">Share</a>
      
      
  <ul class="article-tag-list"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/tags/JavaScript/">JavaScript</a></li></ul>

    </footer>
  </div>
  
</article>


  


  <nav id="page-nav">
    
    <a class="extend prev" rel="prev" href="/">&laquo; 上一页</a><a class="page-number" href="/">1</a><span class="page-number current">2</span><a class="page-number" href="/page/3/">3</a><a class="extend next" rel="next" href="/page/3/">下一页 &raquo;</a>
  </nav>

</section>
        
          <aside id="sidebar">
  
    

  
    
  <div class="widget-wrap">
    <h3 class="widget-title">标签</h3>
    <div class="widget">
      <ul class="tag-list"><li class="tag-list-item"><a class="tag-list-link" href="/tags/JavaScript/">JavaScript</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/JavaScript-解析器/">JavaScript,解析器</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/React/">React</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/Redux/">Redux</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/SlimerJS/">SlimerJS</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/代码管理/">代码管理</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/持续集成/">持续集成</a></li></ul>
    </div>
  </div>


  
    
  <div class="widget-wrap">
    <h3 class="widget-title">标签云</h3>
    <div class="widget tagcloud">
      <a href="/tags/JavaScript/" style="font-size: 20px;">JavaScript</a> <a href="/tags/JavaScript-解析器/" style="font-size: 10px;">JavaScript,解析器</a> <a href="/tags/React/" style="font-size: 16.67px;">React</a> <a href="/tags/Redux/" style="font-size: 10px;">Redux</a> <a href="/tags/SlimerJS/" style="font-size: 10px;">SlimerJS</a> <a href="/tags/代码管理/" style="font-size: 10px;">代码管理</a> <a href="/tags/持续集成/" style="font-size: 13.33px;">持续集成</a>
    </div>
  </div>

  
    
  <div class="widget-wrap">
    <h3 class="widget-title">归档</h3>
    <div class="widget">
      <ul class="archive-list"><li class="archive-list-item"><a class="archive-list-link" href="/archives/2016/09/">九月 2016</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2016/07/">七月 2016</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2016/06/">六月 2016</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2016/03/">三月 2016</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2016/02/">二月 2016</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2015/12/">十二月 2015</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2015/06/">六月 2015</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2015/05/">五月 2015</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2015/04/">四月 2015</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2014/07/">七月 2014</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2014/06/">六月 2014</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2012/11/">十一月 2012</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2012/10/">十月 2012</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2012/07/">七月 2012</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2012/03/">三月 2012</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2012/02/">二月 2012</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2011/10/">十月 2011</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2010/01/">一月 2010</a></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/1970/01/">一月 1970</a></li></ul>
    </div>
  </div>


  
    
  <div class="widget-wrap">
    <h3 class="widget-title">最新文章</h3>
    <div class="widget">
      <ul>
        
          <li>
            <a href="/2016/09/09/space/">空格</a>
          </li>
        
          <li>
            <a href="/2016/07/17/parser/">JavaScript Parser资源总结</a>
          </li>
        
          <li>
            <a href="/2016/06/29/slimerjs/">使用SlimerJS将网页输出为PDF</a>
          </li>
        
          <li>
            <a href="/2016/06/26/code-management/">代码管理</a>
          </li>
        
          <li>
            <a href="/2016/06/18/violet/">violet - 值得一试的写作同步小助手</a>
          </li>
        
      </ul>
    </div>
  </div>

  
</aside>
        
      </div>
      <footer id="footer">
  
  <div class="outer">
    <div id="footer-info" class="inner">
      &copy; 2018 Simon Xu<br>
      Powered by <a href="http://hexo.io/" target="_blank">Hexo</a>
    </div>
  </div>
</footer>
    </div>
    <nav id="mobile-nav">
  
    <a href="/" class="mobile-nav-link">Home</a>
  
    <a href="/archives" class="mobile-nav-link">Archives</a>
  
</nav>
    

<script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>


  <link rel="stylesheet" href="/fancybox/jquery.fancybox.css">
  <script src="/fancybox/jquery.fancybox.pack.js"></script>


<script src="/js/script.js"></script>



  </div>
</body>
</html>